Czy istnieje różnica między „rzutem” a „rzutem ex”?

437

Istnieje kilka postów, które pytają, jaka jest już różnica między tymi dwoma.
(dlaczego muszę nawet o tym wspominać ...)

Ale moje pytanie jest inne, ponieważ nazywam „rzut ex” inną metodą podobną do boga .

public class Program {
    public static void Main(string[] args) {
        try {
            // something
        } catch (Exception ex) {
            HandleException(ex);
        }
    }

    private static void HandleException(Exception ex) {
        if (ex is ThreadAbortException) {
            // ignore then,
            return;
        }
        if (ex is ArgumentOutOfRangeException) { 
            // Log then,
            throw ex;
        }
        if (ex is InvalidOperationException) {
            // Show message then,
            throw ex;
        }
        // and so on.
    }
}

Gdyby try & catchzostały użyte w Main, wtedy throw;użyłbym do ponownego zwrócenia błędu. Ale w powyższym uproszczonym kodzie przechodzą wszystkie wyjątkiHandleException

Czy throw ex;ma taki sam efekt jak dzwonienie, throwgdy jest wywoływany w środku HandleException?

dance2die
źródło
3
Jest różnica, ma to związek z tym, czy lub w jaki sposób pojawia się ślad stosu w wyjątku, ale nie pamiętam, który jest teraz, więc nie wymienię tej odpowiedzi.
Joel Coehoorn
@Joel: Dzięki. Myślę, że użycie wyjątku HandleError to zły pomysł. Chciałem tylko refaktoryzować kod obsługi błędów.
dance2die
1
Trzecim sposobem jest zawarcie
Tim Abell
Możliwy duplikat różnicy między rzutem a rzuceniem nowego wyjątku ()
Michael Freidgeim

Odpowiedzi:

679

Tak, jest różnica;

  • throw exresetuje ślad stosu (aby Twoje błędy mogły się wydawać HandleException)
  • throw nie robi - pierwotny przestępca zostałby zachowany.

    static void Main(string[] args)
    {
        try
        {
            Method2();
        }
        catch (Exception ex)
        {
            Console.Write(ex.StackTrace.ToString());
            Console.ReadKey();
        }
    }
    
    private static void Method2()
    {
        try
        {
            Method1();
        }
        catch (Exception ex)
        {
            //throw ex resets the stack trace Coming from Method 1 and propogates it to the caller(Main)
            throw ex;
        }
    }
    
    private static void Method1()
    {
        try
        {
            throw new Exception("Inside Method1");
        }
        catch (Exception)
        {
            throw;
        }
    }
Marc Gravell
źródło
28
Aby nieco rozwinąć odpowiedź Marca, możesz znaleźć więcej szczegółów tutaj: geekswithblogs.net/sdorman/archive/2007/08/20/…
Scott Dorman
3
@Shaul; nie, nie jest. Podałem szczegóły w komentarzu do twojego postu.
Marc Gravell
1
@Marc Gravell - przepraszam, miałeś rację. Przepraszam za opinię; jest już za późno, bym cofnął ... :(
Shaul Behr
3
@Marc: Wygląda na to, że rzut zachowuje oryginalnego przestępcę TYLKO, jeśli rzut nie jest zgodny z metodą, w której został zgłoszony wyjątek (patrz to pytanie: stackoverflow.com/questions/5152265/… )
Brann
3
@ScottDorman Wygląda na to, że Twój link nie został poprawnie przesłany po migracji blogu. Wygląda na to, że teraz tu mieszka . Edycja: Hej, czekaj, to twój blog! Napraw własne linki! ; ^ D
ruffin
96

(Napisałem wcześniej, a @Marc Gravell mnie poprawił)

Oto przykład różnicy:

static void Main(string[] args) {
    try {
        ThrowException1(); // line 19
    } catch (Exception x) {
        Console.WriteLine("Exception 1:");
        Console.WriteLine(x.StackTrace);
    }
    try {
        ThrowException2(); // line 25
    } catch (Exception x) {
        Console.WriteLine("Exception 2:");
        Console.WriteLine(x.StackTrace);
    }
}

private static void ThrowException1() {
    try {
        DivByZero(); // line 34
    } catch {
        throw; // line 36
    }
}
private static void ThrowException2() {
    try {
        DivByZero(); // line 41
    } catch (Exception ex) {
        throw ex; // line 43
    }
}

private static void DivByZero() {
    int x = 0;
    int y = 1 / x; // line 49
}

a oto wynik:

Exception 1:
   at UnitTester.Program.DivByZero() in <snip>\Dev\UnitTester\Program.cs:line 49
   at UnitTester.Program.ThrowException1() in <snip>\Dev\UnitTester\Program.cs:line 36
   at UnitTester.Program.TestExceptions() in <snip>\Dev\UnitTester\Program.cs:line 19

Exception 2:
   at UnitTester.Program.ThrowException2() in <snip>\Dev\UnitTester\Program.cs:line 43
   at UnitTester.Program.TestExceptions() in <snip>\Dev\UnitTester\Program.cs:line 25

Widać, że w wyjątku 1 ślad stosu wraca do DivByZero()metody, podczas gdy w wyjątku 2 tak nie jest.

Zwróć jednak uwagę, że numer wiersza pokazany w ThrowException1()i ThrowException2()jest numerem wiersza throwinstrukcji, a nie numerem wiersza połączenia DivByZero(), co prawdopodobnie ma sens teraz, gdy trochę o tym myślę ...

Wyjście w trybie zwolnienia

Wyjątek 1:

at ConsoleAppBasics.Program.ThrowException1()
at ConsoleAppBasics.Program.Main(String[] args)

Wyjątek 2:

at ConsoleAppBasics.Program.ThrowException2()
at ConsoleAppBasics.Program.Main(String[] args)

Czy zachowuje oryginalny stackTrace tylko w trybie debugowania?

Shaul Behr
źródło
1
Jest tak, ponieważ proces optymalizacji kompilatora obejmuje krótkie metody, takie jak DevideByZero, więc śledzenie stosu jest takie samo. może powinieneś zamieścić to jako własne pytanie
Menahem
42

Pozostałe odpowiedzi są całkowicie poprawne, ale myślę, że ta odpowiedź zawiera dodatkowe szczegóły.

Rozważ ten przykład:

using System;

static class Program {
  static void Main() {
    try {
      ThrowTest();
    } catch (Exception e) {
      Console.WriteLine("Your stack trace:");
      Console.WriteLine(e.StackTrace);
      Console.WriteLine();
      if (e.InnerException == null) {
        Console.WriteLine("No inner exception.");
      } else {
        Console.WriteLine("Stack trace of your inner exception:");
        Console.WriteLine(e.InnerException.StackTrace);
      }
    }
  }

  static void ThrowTest() {
    decimal a = 1m;
    decimal b = 0m;
    try {
      Mult(a, b);  // line 34
      Div(a, b);   // line 35
      Mult(b, a);  // line 36
      Div(b, a);   // line 37
    } catch (ArithmeticException arithExc) {
      Console.WriteLine("Handling a {0}.", arithExc.GetType().Name);

      //   uncomment EITHER
      //throw arithExc;
      //   OR
      //throw;
      //   OR
      //throw new Exception("We handled and wrapped your exception", arithExc);
    }
  }

  static void Mult(decimal x, decimal y) {
    decimal.Multiply(x, y);
  }
  static void Div(decimal x, decimal y) {
    decimal.Divide(x, y);
  }
}

Jeśli usuniesz komentarz z throw arithExc;wiersza, wynikiem będzie:

Handling a DivideByZeroException.
Your stack trace:
   at Program.ThrowTest() in c:\somepath\Program.cs:line 44
   at Program.Main() in c:\somepath\Program.cs:line 9

No inner exception.

Z pewnością utraciłeś informacje o tym, gdzie zdarzył się ten wyjątek. Jeśli zamiast tego użyjesz throw;linii, otrzymasz:

Handling a DivideByZeroException.
Your stack trace:
   at System.Decimal.FCallDivide(Decimal& d1, Decimal& d2)
   at System.Decimal.Divide(Decimal d1, Decimal d2)
   at Program.Div(Decimal x, Decimal y) in c:\somepath\Program.cs:line 58
   at Program.ThrowTest() in c:\somepath\Program.cs:line 46
   at Program.Main() in c:\somepath\Program.cs:line 9

No inner exception.

Jest to o wiele lepsze, ponieważ teraz widzisz, że była to Program.Divmetoda, która spowodowała problemy. Ale nadal trudno jest sprawdzić, czy ten problem pochodzi z linii 35 czy linii 37 w trybloku.

Jeśli użyjesz trzeciej alternatywy, zawierającej zewnętrzny wyjątek, nie stracisz żadnych informacji:

Handling a DivideByZeroException.
Your stack trace:
   at Program.ThrowTest() in c:\somepath\Program.cs:line 48
   at Program.Main() in c:\somepath\Program.cs:line 9

Stack trace of your inner exception:
   at System.Decimal.FCallDivide(Decimal& d1, Decimal& d2)
   at System.Decimal.Divide(Decimal d1, Decimal d2)
   at Program.Div(Decimal x, Decimal y) in c:\somepath\Program.cs:line 58
   at Program.ThrowTest() in c:\somepath\Program.cs:line 35

W szczególności widać, że to linia 35 prowadzi do problemu. Wymaga to jednak od ludzi przeszukiwania InnerExceptioni używanie pośrednich wyjątków w prostych przypadkach wydaje się nieco pośrednie.

W tym wpisie na blogu zachowują numer linii (wiersz bloku try), wywołując (poprzez odbicie) internalmetodę intencji InternalPreserveStackTrace()na Exceptionobiekcie. Ale nie jest miło używać takiego odbicia (.NET Framework może internalpewnego dnia zmienić członków bez ostrzeżenia).

Jeppe Stig Nielsen
źródło
6

zrozumiemy różnicę między rzutem a rzutem ex. Słyszałem, że w wielu wywiadach .net pytany jest ten często zadawany.

Aby przedstawić przegląd tych dwóch terminów, rzut i rzut ex służą zarówno do zrozumienia, gdzie wystąpił wyjątek. Rzut ex przepisuje ślad stosu wyjątku bez względu na to, gdzie rzeczywiście został zgłoszony.

Rozumiem na przykładzie.

Rozumiem pierwszy rzut.

static void Main(string[] args) {
    try {
        M1();
    } catch (Exception ex) {
        Console.WriteLine(" -----------------Stack Trace Hierarchy -----------------");
        Console.WriteLine(ex.StackTrace.ToString());
        Console.WriteLine(" ---------------- Method Name / Target Site -------------- ");
        Console.WriteLine(ex.TargetSite.ToString());
    }
    Console.ReadKey();
}

static void M1() {
    try {
        M2();
    } catch (Exception ex) {
        throw;
    };
}

static void M2() {
    throw new DivideByZeroException();
}

wynik powyższego jest poniżej.

pokazuje pełną hierarchię i nazwę metody, w której faktycznie zgłoszono wyjątek. Jest to M2 -> M2. wraz z numerami linii

wprowadź opis zdjęcia tutaj

Po drugie .. pozwala zrozumieć przez rzut ex. Wystarczy zamienić rzut na rzut ex w bloku catch metody M2. jak poniżej.

wprowadź opis zdjęcia tutaj

kod wyjściowy rzutu ex jest taki jak poniżej.

wprowadź opis zdjęcia tutaj

Możesz zobaczyć różnicę w wynikach. Rzut ex po prostu ignoruje całą poprzednią hierarchię i resetuje śledzenie stosu za pomocą linii / metody, w której zapisywany jest rzut ex.

Mahesh
źródło
5

Kiedy to zrobisz throw ex, ten zgłoszony wyjątek staje się „oryginalny”. Tak więc nie będzie tam wszystkich poprzednich śladów stosu.

Jeśli to zrobisz throw, wyjątek po prostu pójdzie wzdłuż linii, a otrzymasz pełny ślad stosu.

GR7
źródło
4

Nie, spowoduje to, że wyjątek będzie miał inny ślad stosu. Tylko użycie throww catchmodule obsługi obiektu bez wyjątku pozostawi ślad stosu bez zmian.

Możesz zwrócić wartość logiczną z HandleException, niezależnie od tego, czy wyjątek zostanie ponownie wygenerowany, czy nie.

Lucero
źródło
4

MSDN oznacza :

Po zgłoszeniu wyjątku część informacji, które zawiera, to ślad stosu. Śledzenie stosu to lista hierarchii wywołań metod, która rozpoczyna się od metody zgłaszającej wyjątek, a kończy na metodzie wychwytującej wyjątek. Jeśli wyjątek zostanie ponownie zgłoszony przez określenie wyjątku w instrukcji throw, śledzenie stosu zostanie ponownie uruchomione przy bieżącej metodzie, a lista wywołań metod między oryginalną metodą, która zgłosiła wyjątek, a bieżącą metodą zostanie utracona. Aby zachować oryginalne informacje śledzenia stosu z wyjątkiem, użyj instrukcji throw bez określania wyjątku.

TAK JAK
źródło
2

Spójrz tutaj: http://blog-mstechnology.blogspot.de/2010/06/throw-vs-throw-ex.html

Rzut :

try 
{
    // do some operation that can fail
}
catch (Exception ex)
{
    // do some local cleanup
    throw;
}

Zachowuje informacje o stosie z wyjątkiem

Nazywa się to „Rethrow”

Jeśli chcesz zgłosić nowy wyjątek,

throw new ApplicationException("operation failed!");

Rzuć Ex :

try
{
    // do some operation that can fail
}
catch (Exception ex)
{
    // do some local cleanup
    throw ex;
}

Nie będzie wysyłał informacji o stosie z wyjątkiem

Nazywa się to „Breaking the Stack”

Jeśli chcesz zgłosić nowy wyjątek,

throw new ApplicationException("operation failed!",ex);
Aaaaaaaa
źródło
0

Aby dać ci inną perspektywę, użycie rzucania jest szczególnie przydatne, jeśli udostępniasz interfejs API klientowi i chcesz podać szczegółowe informacje o stosie śledzenia dla swojej biblioteki wewnętrznej. Korzystając z rzutu tutaj, otrzymam ślad stosu w tym przypadku biblioteki System.IO.File dla File.Delete. Jeśli użyję throw ex, informacje te nie zostaną przekazane mojemu handlerowi.

static void Main(string[] args) {            
   Method1();            
}

static void Method1() {
    try {
        Method2();
    } catch (Exception ex) {
        Console.WriteLine("Exception in Method1");             
    }
}

static void Method2() {
    try {
        Method3();
    } catch (Exception ex) {
        Console.WriteLine("Exception in Method2");
        Console.WriteLine(ex.TargetSite);
        Console.WriteLine(ex.StackTrace);
        Console.WriteLine(ex.GetType().ToString());
    }
}

static void Method3() {
    Method4();
}

static void Method4() {
    try {
        System.IO.File.Delete("");
    } catch (Exception ex) {
        // Displays entire stack trace into the .NET 
        // or custom library to Method2() where exception handled
        // If you want to be able to get the most verbose stack trace
        // into the internals of the library you're calling
        throw;                
        // throw ex;
        // Display the stack trace from Method4() to Method2() where exception handled
    }
}
Charles Owen
źródło
-1
int a = 0;
try {
    int x = 4;
    int y ;
    try {
        y = x / a;
    } catch (Exception e) {
        Console.WriteLine("inner ex");
        //throw;   // Line 1
        //throw e;   // Line 2
        //throw new Exception("devide by 0");  // Line 3
    }
} catch (Exception ex) {
    Console.WriteLine(ex);
    throw ex;
}
  1. jeśli wszystkie linie 1, 2 i 3 są skomentowane - Wyjście - wewnętrzna ex

  2. jeśli wszystkie wiersze 2 i 3 są skomentowane - Wyjście - wewnętrzny ex System.DevideByZeroException: {"Próbowano podzielić przez zero."} ---------

  3. jeśli wszystkie wiersze 1 i 2 są skomentowane - Wyjście - wewnętrzny ex System. Wyjątek: dzielenie przez 0 ----

  4. jeśli wszystkie wiersze 1 i 3 są skomentowane - Wyjście - wewnętrzny ex System.DevideByZeroException: {"Próbowano podzielić przez zero."} ---------

i StackTrace zostanie zresetowany w przypadku wyrzucenia ex;

Bhanu pratap
źródło