C'è una differenza tra "lancio" e "lancio ex"?


437

Ci sono alcuni post che chiedono quale sia la differenza tra questi due.
(perché devo anche menzionarlo ...)

Ma la mia domanda è diversa in un modo che sto chiamando "buttare ex" in un altro metodo di gestione simile a un dio .

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.
    }
}

Se try & catchfossero stati usati in Main, allora throw;userei per riproporre l'errore. Ma nel codice semplificato sopra, passano tutte le eccezioniHandleException

Non throw ex;ha lo stesso effetto di una chiamata throwquando viene chiamato dentro HandleException?


3
C'è una differenza, ha a che fare con se o come appare la traccia dello stack nell'eccezione, ma non ricordo quale sia quale in questo momento quindi non elencherò questa risposta.
Joel Coehoorn,

@ Gioele: grazie. Immagino che usare l'eccezione HandleError sia una cattiva idea. Volevo solo refactoring del codice di gestione degli errori.
dance2die,

1
Il terzo modo è racchiudere una nuova eccezione e riproporre timwise.blogspot.co.uk/2014/05/…
Tim Abell,

Risposte:


679

Sì, c'è differenza;

  • throw exreimposta la traccia dello stack (quindi i tuoi errori sembrano provenire da HandleException)
  • throw no - l'autore del reato originale sarebbe preservato.

    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;
        }
    }

28
Per espandere un po 'la risposta di Marc, puoi trovare maggiori dettagli qui: geekswithblogs.net/sdorman/archive/2007/08/20/…
Scott Dorman

3
@Shaul; no, non lo è. Ho fornito dettagli in un commento al tuo post.
Marc Gravell

1
@Marc Gravell - le mie scuse, avevi ragione. Mi dispiace per il downvote; è troppo tardi per me annullare ... :(
Shaul Behr,

3
@Marc: sembra che il tiro preservi l'autore del reato SOLO se il tiro non è nel metodo in cui è stata lanciata l'eccezione iniziale (vedi questa domanda: stackoverflow.com/questions/5152265/… )
Brann

3
@ScottDorman Sembra che il tuo link non venga inoltrato correttamente dopo la migrazione di un blog. Sembra che ora viva qui . Modifica: Ehi, aspetta, questo è il tuo blog! Correggi i tuoi collegamenti! ; ^ D
ruffin

96

(Ho pubblicato prima e @Marc Gravell mi ha corretto)

Ecco una dimostrazione della differenza:

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
}

ed ecco l'output:

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

Si può vedere che nell'eccezione 1, la traccia dello stack ritorna al DivByZero()metodo, mentre nell'eccezione 2 non lo fa.

Prendi nota, tuttavia, che il numero di riga mostrato in ThrowException1()ed ThrowException2()è il numero di riga throwdell'istruzione, non il numero di riga della chiamata a DivByZero(), il che probabilmente ha senso ora che ci penso un po '...

Uscita in modalità di rilascio

Eccezione 1:

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

Eccezione 2:

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

Mantiene lo stackTrace originale solo in modalità debug?


1
È perché il processo di ottimizzazione del compilatore incorpora metodi brevi come DevideByZero, quindi la traccia dello stack è la stessa. forse dovresti pubblicare questo come una domanda da solo
Menahem,

42

Le altre risposte sono del tutto corrette, ma questa risposta fornisce qualche dettaglio in più, credo.

Considera questo esempio:

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);
  }
}

Se si decommenta la throw arithExc;riga, l'output è:

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.

Certamente, hai perso informazioni su dove si è verificata tale eccezione. Se invece usi la throw;linea, ecco cosa ottieni:

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.

Questo è molto meglio, perché ora vedi che è stato il Program.Divmetodo a causarti problemi. Ma è ancora difficile vedere se questo problema proviene dalla linea 35 o dalla linea 37 nel tryblocco.

Se usi la terza alternativa, racchiudendo un'eccezione esterna, non perdi alcuna informazione:

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

In particolare puoi vedere che è la linea 35 che porta al problema. Tuttavia, ciò richiede che le persone effettuino ricerche in InnerException, e sembra in qualche modo indiretto usare eccezioni interne in casi semplici.

In questo post conservano il numero di riga (riga del blocco try) chiamando (attraverso la riflessione) il internalmetodo intance InternalPreserveStackTrace()sul Exceptionoggetto. Ma non è bello usare la riflessione in questo modo (.NET Framework potrebbe cambiare i loro internalmembri un giorno senza preavviso).


6

capiamo la differenza tra lancio e lancio ex. Ho sentito che in molte interviste .net viene chiesto questo interrogativo comune.

Solo per dare una panoramica di questi due termini, lancio e lancio ex sono entrambi usati per capire dove si è verificata l'eccezione. Throw ex riscrive la traccia dello stack dell'eccezione indipendentemente da dove sia stata effettivamente lanciata.

Comprendiamo con un esempio.

Comprendiamo il primo lancio.

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();
}

l'output di quanto sopra è inferiore.

mostra la gerarchia completa e il nome del metodo in cui è stata effettivamente generata l'eccezione .. è M2 -> M2. insieme ai numeri di riga

inserisci qui la descrizione dell'immagine

In secondo luogo .. facciamo capire lanciando ex. Sostituisci semplicemente il tiro con il tiro ex nel blocco catch del metodo M2. come sotto.

inserisci qui la descrizione dell'immagine

l'output del codice di lancio è come di seguito.

inserisci qui la descrizione dell'immagine

Puoi vedere la differenza nell'output. Throw ex ignora solo tutta la gerarchia precedente e reimposta la traccia dello stack con la linea / il metodo in cui è scritto throw ex.


5

Quando lo fai throw ex, quell'eccezione generata diventa quella "originale". Quindi tutta la traccia dello stack precedente non sarà presente.

Se lo fai throw, l'eccezione si limita a scendere e otterrai la traccia dello stack completo.


4

No, ciò farà sì che l'eccezione abbia una traccia di stack diversa. Solo l'uso di un throwoggetto senza eccezioni nel catchgestore lascerà invariata la traccia dello stack.

È possibile che si desideri restituire un valore booleano da HandleException se l'eccezione deve essere riproposta o meno.


4

MSDN significa :

Una volta generata un'eccezione, parte delle informazioni che trasporta è la traccia dello stack. La traccia dello stack è un elenco della gerarchia di chiamate del metodo che inizia con il metodo che genera l'eccezione e termina con il metodo che rileva l'eccezione. Se viene generata un'eccezione specificando l'eccezione nell'istruzione throw, la traccia dello stack viene riavviata nel metodo corrente e l'elenco di chiamate al metodo tra il metodo originale che ha generato l'eccezione e il metodo corrente viene perso. Per mantenere le informazioni di traccia dello stack originale con l'eccezione, utilizzare l'istruzione throw senza specificare l'eccezione.


2

Guarda qui: http://blog-mstechnology.blogspot.de/2010/06/throw-vs-throw-ex.html

Lancio :

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

Conserva le informazioni sullo Stack con Eccezione

Questo si chiama "Rethrow"

Se vuoi lanciare una nuova eccezione,

throw new ApplicationException("operation failed!");

Lancia Ex :

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

Non invierà informazioni sullo stack con eccezione

Questo si chiama "Breaking the Stack"

Se vuoi lanciare una nuova eccezione,

throw new ApplicationException("operation failed!",ex);

0

Per darti una prospettiva diversa su questo, l'uso di throw è particolarmente utile se stai fornendo un'API a un client e vuoi fornire informazioni dettagliate sullo stack stack per la tua libreria interna. Usando butta qui, otterrei la traccia dello stack in questo caso della libreria System.IO.File per File.Delete. Se uso il tiro ex, allora quelle informazioni non saranno passate al mio gestore.

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
    }
}

-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. se tutte le righe 1, 2 e 3 sono commentate - Output - inner ex

  2. se vengono commentate tutte le righe 2 e 3 - Output - interno ex System.DevideByZeroException: {"Tentativo di dividere per zero."} ---------

  3. se tutte le righe 1 e 2 sono commentate - Output - inner ex System.Exception: divide by 0 ----

  4. se vengono commentate tutte le righe 1 e 3 - Output - interno ex System.DevideByZeroException: {"Tentativo di dividere per zero."} ---------

e StackTrace verrà resettato in caso di lancio ex;

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.