różnica między rzutem a rzutem new Exception ()


164

jaka jest różnica pomiędzy

try { ... }
catch{ throw } 

i

try{ ... }
catch(Exception e) {throw new Exception(e.message) } 

Niezależnie od tego, że druga pokazuje wiadomość?


51
Drugi fragment to jedna z najbardziej złych (ale niewinnych) linii kodu, jakie kiedykolwiek widziałem.
SLaks

Odpowiedzi:


259

throw; ponownie zgłasza oryginalny wyjątek i zachowuje jego oryginalny ślad stosu.

throw ex;zgłasza oryginalny wyjątek, ale resetuje ślad stosu, niszcząc wszystkie informacje o śladzie stosu aż do catchbloku.


NIGDY nie piszthrow ex;


throw new Exception(ex.Message);jest jeszcze gorszy. Tworzy zupełnie nową Exceptioninstancję, tracąc oryginalny ślad stosu wyjątku, a także jego typ. (np IOException.).
Ponadto niektóre wyjątki zawierają dodatkowe informacje (np ArgumentException.ParamName.).
throw new Exception(ex.Message); zniszczy te informacje.

W niektórych przypadkach możesz chcieć zawinąć wszystkie wyjątki w niestandardowy obiekt wyjątku, aby można było podać dodatkowe informacje o tym, co robił kod, gdy wyjątek został zgłoszony.

Aby to zrobić, zdefiniuj nową klasę, która dziedziczy Exception, dodaj wszystkie cztery konstruktory wyjątków i opcjonalnie dodatkowy konstruktor, który pobiera, InnerExceptiona także dodatkowe informacje, i wyrzuć nową klasę wyjątku, przekazując ją exjako InnerExceptionparametr . Przekazując oryginał InnerException, zachowujesz wszystkie właściwości oryginalnego wyjątku, w tym ślad stosu.


24
„wrzuć nowy wyjątek (ex); jest jeszcze gorszy.”: Nie zgadzam się z tym. Czasami chcesz zmienić typ wyjątku, a następnie zachowanie oryginalnego wyjątku jako wyjątku wewnętrznego jest najlepszym, co możesz zrobić. Chociaż powinno być throw new MyCustomException(myMessage, ex);oczywiście.
Dirk Vollmar

9
@ 0xA3: Miałem na myśli ex.Message, co jest gorsze.
SLaks

6
Oprócz implementacji standardowych konstruktorów należy również tworzyć niestandardowe wyjątki [Serializable()].
Dirk Vollmar

21
Hej, zagraliśmy cię jak wyjątki, więc umieściliśmy wyjątek w twoim wyjątku, abyś mógł złapać, gdy łapiesz.
Darth Continent

2
@SLaks: Gdy throw;rzeczywisty numer wiersza, w którym wystąpił wyjątek, zostanie zastąpiony numerem wiersza throw;. Jak sobie z tym radzisz? stackoverflow.com/questions/2493779/…
Eric J.

34

Pierwsza zachowuje oryginalny ślad stosu:

try { ... }
catch
{
    // Do something.
    throw;
}

Drugi umożliwia zmianę rodzaju wyjątku i / lub wiadomości i innych danych:

try { ... } catch (Exception e)
{
    throw new BarException("Something broke!");
}

Istnieje również trzeci sposób, w którym można przekazać wyjątek wewnętrzny:

try { ... }
catch (FooException e) {
    throw new BarException("foo", e);
} 

Polecam użycie:

  • pierwszy, jeśli chcesz wykonać porządki w sytuacji błędu bez niszczenia informacji lub dodawania informacji o błędzie.
  • trzecia, jeśli chcesz dodać więcej informacji o błędzie.
  • druga, jeśli chcesz ukryć informacje (przed niezaufanymi użytkownikami).

6

Jeszcze jedna kwestia, której nikt nie zauważył:

Jeśli nic nie zrobisz w swoim bloku catch {}, próba ... złapania jest bezcelowa. Widzę to cały czas:

try 
{
  //Code here
}
catch
{
    throw;
}

Albo gorzej:

try 
{
  //Code here
}
catch(Exception ex)
{
    throw ex;
}

Najgorsze jeszcze:

try 
{
  //Code here
}
catch(Exception ex)
{
    throw new System.Exception(ex.Message);
}

Zgadzam się, chyba że masz klauzulę ostateczną.
Toni Rossmann,

1
@ToniRossmann W tym przypadku użyłbym try..finally bez haczyka, chyba że robisz coś innego niż rzucanie;
JLWarlow

4

throwponownie zgłasza przechwycony wyjątek, zachowując ślad stosu, jednocześnie throw new Exceptiontracąc niektóre szczegóły przechwyconego wyjątku.

Zwykle używałbyś throwsamego siebie do rejestrowania wyjątku bez pełnej obsługi go w tym momencie.

BlackWasp ma dobry artykuł zatytułowany Throwing Exceptions in C # .


4

Zgłoszenie nowego wyjątku powoduje zniszczenie aktualnego śladu stosu.

throw;zachowa oryginalny ślad stosu i prawie zawsze jest bardziej przydatna. Wyjątkiem od tej reguły jest sytuacja, gdy chcesz zawinąć wyjątek w niestandardowy wyjątek własny. Powinieneś wtedy:

catch(Exception e)
{
    throw new CustomException(customMessage, e);
}

3

throwsłuży do ponownego zgłaszania przechwyconego wyjątku. Może to być przydatne, jeśli chcesz zrobić coś z wyjątkiem przed przekazaniem go do łańcucha wywołań.

Użycie throwbez żadnych argumentów zachowuje stos wywołań do celów debugowania.


0

Jeśli chcesz, możesz zgłosić nowy wyjątek, z oryginalnym ustawionym jako wyjątek wewnętrzny.


0

Twój drugi przykład zresetuje ślad stosu wyjątku. Pierwsza z nich najdokładniej zachowuje pochodzenie wyjątku. Rozpakowałeś również oryginalny typ, który jest kluczem do ustalenia, co faktycznie poszło nie tak ... Jeśli drugi jest wymagany do funkcjonalności - np. Aby dodać rozszerzone informacje lub ponownie zawinąć specjalnym typem, takim jak niestandardowy 'HandleableException', po prostu upewnij się, że właściwość InnerException jest również ustawiona!


Tak, to jedno z tych pytań, na które trzeba szybko pisać. ;)
Robert Harvey

0

Najważniejsza różnica polega na tym, że drugie wyrażenie usuwa typ wyjątku. Typ wyjątku odgrywa istotną rolę w wyłapywaniu wyjątków:

public void MyMethod ()
{
    // both can throw IOException
    try { foo(); } catch { throw; }
    try { bar(); } catch(E) {throw new Exception(E.message); }
}

(...)

try {
    MyMethod ();
} catch (IOException ex) {
    Console.WriteLine ("Error with I/O"); // [1]
} catch (Exception ex) {
    Console.WriteLine ("Other error");    // [2]
}

Jeśli foo()rzuca IOException, [1]blok catch złapie wyjątek. Ale kiedy zostanie bar()rzucona IOException, zostanie zamieniona na zwykłą Exceptionmrówkę, która nie zostanie złapana przez [1]blok catch.


0

throw lub throw ex, oba służą do zgłaszania lub ponownego zgłaszania wyjątku, kiedy po prostu rejestrujesz informacje o błędzie i nie chcesz wysyłać żadnych informacji z powrotem do dzwoniącego, po prostu rejestrujesz błąd w catch i opuść. Ale jeśli chcesz wysłać znaczące informacje o wyjątku do dzwoniącego, którego używasz throw lub throw ex. Teraz różnica między throw i throw ex polega na tym, że throw zachowuje ślad stosu i inne informacje, ale throw ex tworzy nowy obiekt wyjątku, a zatem oryginalny ślad stosu zostaje utracony. Kiedy więc powinniśmy użyć throw i throw e, jest jeszcze kilka sytuacji, w których możesz chcieć ponownie zgłosić wyjątek, np. Zresetować informacje o stosie wywołań. Na przykład, jeśli metoda znajduje się w bibliotece i chcesz ukryć szczegóły biblioteki przed kodem wywołującym, niekoniecznie chcesz, aby stos wywołań zawierał informacje o metodach prywatnych w bibliotece. W takim przypadku możesz przechwycić wyjątki w metodach publicznych biblioteki, a następnie wyrzucić je ponownie, tak aby stos wywołań zaczynał się od tych metod publicznych.


0

Rzucać; Wywołaj ponownie oryginalny wyjątek i zachowaj jego typ.

Rzuć nowy wyjątek (); Ponownie wyślij oryginalny typ wyjątku i zresetuj ślad stosu wyjątków

Rzuć ex; Zresetuj ślad stosu wyjątków i zresetuj typ wyjątku


-1

Żadna z odpowiedzi tutaj nie pokazuje różnicy, co może być pomocne dla osób, które mają trudności ze zrozumieniem różnicy. Rozważ ten przykładowy kod:

using System;
using System.Collections.Generic;

namespace ExceptionDemo
{
   class Program
   {
      static void Main(string[] args)
      {
         void fail()
         {
            (null as string).Trim();
         }

         void bareThrow()
         {
            try
            {
               fail();
            }
            catch (Exception e)
            {
               throw;
            }
         }

         void rethrow()
         {
            try
            {
               fail();
            }
            catch (Exception e)
            {
               throw e;
            }
         }

         void innerThrow()
         {
            try
            {
               fail();
            }
            catch (Exception e)
            {
               throw new Exception("outer", e);
            }
         }

         var cases = new Dictionary<string, Action>()
         {
            { "Bare Throw:", bareThrow },
            { "Rethrow", rethrow },
            { "Inner Throw", innerThrow }
         };

         foreach (var c in cases)
         {
            Console.WriteLine(c.Key);
            Console.WriteLine(new string('-', 40));
            try
            {
               c.Value();
            } catch (Exception e)
            {
               Console.WriteLine(e.ToString());
            }
         }
      }
   }
}

Który generuje następujący wynik:

Bare Throw:
----------------------------------------
System.NullReferenceException: Object reference not set to an instance of an object.
   at ExceptionDemo.Program.<Main>g__fail|0_0() in C:\...\ExceptionDemo\Program.cs:line 12
   at ExceptionDemo.Program.<>c.<Main>g__bareThrow|0_1() in C:\...\ExceptionDemo\Program.cs:line 19
   at ExceptionDemo.Program.Main(String[] args) in C:\...\ExceptionDemo\Program.cs:line 64

Rethrow
----------------------------------------
System.NullReferenceException: Object reference not set to an instance of an object.
   at ExceptionDemo.Program.<>c.<Main>g__rethrow|0_2() in C:\...\ExceptionDemo\Program.cs:line 35
   at ExceptionDemo.Program.Main(String[] args) in C:\...\ExceptionDemo\Program.cs:line 64

Inner Throw
----------------------------------------
System.Exception: outer ---> System.NullReferenceException: Object reference not set to an instance of an object.
   at ExceptionDemo.Program.<Main>g__fail|0_0() in C:\...\ExceptionDemo\Program.cs:line 12
   at ExceptionDemo.Program.<>c.<Main>g__innerThrow|0_3() in C:\...\ExceptionDemo\Program.cs:line 43
   --- End of inner exception stack trace ---
   at ExceptionDemo.Program.<>c.<Main>g__innerThrow|0_3() in C:\...\ExceptionDemo\Program.cs:line 47
   at ExceptionDemo.Program.Main(String[] args) in C:\...\ExceptionDemo\Program.cs:line 64

Sama rzut, jak wskazano w poprzednich odpowiedziach, wyraźnie pokazuje zarówno oryginalną linię kodu, która się nie powiodła (linia 12), jak i dwa inne punkty aktywne na stosie wywołań, gdy wystąpił wyjątek (linie 19 i 64).

Dane wyjściowe przypadku ponownego rzucenia pokazują, dlaczego jest to problem. Gdy wyjątek zostanie ponownie wygenerowany w ten sposób, wyjątek nie będzie zawierał oryginalnych informacji o stosie. Zauważ, że uwzględnione są tylko throw e(linia 35) i najbardziej zewnętrzny punkt stosu wywołań (linia 64). Trudno byłoby wyśledzić metodę fail () jako źródło problemu, jeśli w ten sposób rzucisz wyjątki.

Ostatni przypadek (innerThrow) jest najbardziej rozbudowany i zawiera więcej informacji niż którykolwiek z powyższych. Ponieważ tworzymy nowy wyjątek, mamy szansę dodać informacje kontekstowe (wiadomość „zewnętrzną”, ale możemy również dodać do słownika .Data nowy wyjątek), jak również zachować wszystkie informacje z oryginału wyjątek (w tym łącza pomocy, słownik danych itp.).

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.