Existe uma diferença entre "jogar" e "jogar ex"?


437

Existem alguns posts que perguntam qual a diferença entre esses dois já.
(Por que eu tenho que mencionar isso ...)

Mas minha pergunta é diferente de uma maneira que estou chamando de "throw ex" em outro método de manipulação semelhante a um deus .

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 & catchfosse usado no Main, eu usaria throw;para repetir o erro. Mas no código simplificado acima, todas as exceções passam porHandleException

Será que throw ex;tem o mesmo efeito que chamar throwquando chamado dentro HandleException?


3
Há uma diferença: tem a ver com a aparência ou não do rastreamento da pilha na exceção, mas não me lembro qual é qual agora, então não listarei isso como resposta.
Joel Coehoorn

@ Joel: Obrigado. Eu acho que usar exceção HandleError é uma má idéia. Eu só queria refatorar algum código de manipulação de erros.
dance2die

1
A terceira maneira é quebrar uma nova exceção e relançar timwise.blogspot.co.uk/2014/05/…
Tim Abell

Respostas:


679

Sim, há uma diferença;

  • throw exredefine o rastreamento de pilha (para que seus erros pareçam se originar HandleException)
  • throw não - o agressor original seria preservado.

    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
Para expandir a resposta de Marc um pouco, você pode encontrar mais detalhes aqui: geekswithblogs.net/sdorman/archive/2007/08/20/...
Scott Dorman

3
@Shaul; não é não. Forneci detalhes em um comentário à sua postagem.
Marc Gravell

1
@ Marc Gravell - minhas desculpas, você estava certo. Desculpe pelo voto negativo; que seja tarde demais para mim para desfazer ... :(
Shaul Behr

3
@ Marc: Parece que lance preserva o ofensor original só se o lance não está no método em que a exceção inicial foi lançada (ver esta questão: stackoverflow.com/questions/5152265/... )
Brann

3
@ScottDorman Parece que seu link não está sendo encaminhado corretamente após uma migração de blog. Parece que agora mora aqui . Edit: Ei, espere, esse é o seu blog! Corrija seus próprios links! ; ^ D
ruffin

96

(Publiquei anteriormente e @ Marc Gravell me corrigiu)

Aqui está uma demonstração da diferença:

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
}

e aqui está a saída:

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

Você pode ver que, na exceção 1, o rastreamento de pilha volta ao DivByZero()método, enquanto na exceção 2, não.

Observe, porém, que o número da linha mostrado em ThrowException1()e ThrowException2()é o número da linha da throwinstrução, não o número da linha da chamada DivByZero(), o que provavelmente faz sentido agora que penso um pouco sobre isso ...

Saída no modo Release

Exceção 1:

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

Exceção 2:

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

Ele mantém o stackTrace original apenas no modo de depuração?


1
É porque o processo de otimização do compilador alinha métodos curtos, como DevideByZero, portanto, o rastreamento da pilha é o mesmo. talvez você deva postar isso como uma pergunta por si só
Menahem

42

As outras respostas estão inteiramente corretas, mas acho que essa resposta fornece detalhes adicionais.

Considere este exemplo:

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 você descomentar a throw arithExc;linha, sua saída é:

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, você perdeu informações sobre onde essa exceção ocorreu. Se, em vez disso, você usar a throw;linha, é isso que você obtém:

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.

Isso é muito melhor, porque agora você vê que foi o Program.Divmétodo que lhe causou problemas. Mas ainda é difícil ver se esse problema vem da linha 35 ou da linha 37 do trybloco.

Se você usar a terceira alternativa, agrupando uma exceção externa, você não perde nenhuma informação:

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

Em particular, você pode ver que é a linha 35 que leva ao problema. No entanto, isso requer que as pessoas pesquisem o InnerException, e parece um pouco indireto usar exceções internas em casos simples.

Em este post eles preservam o número da linha (linha do bloco try) pelo telefone (através da reflexão) o internalmétodo intance InternalPreserveStackTrace()sobre o Exceptionobjeto. Mas não é legal usar reflexões como essa (o .NET Framework pode alterar seus internalmembros algum dia sem aviso prévio).


6

vamos entender a diferença entre jogar e jogar ex. Ouvi dizer que, em muitas entrevistas .net, esse pedido comum está sendo solicitado.

Apenas para fornecer uma visão geral desses dois termos, throw e throw ex são usados ​​para entender onde a exceção ocorreu. Throw ex reescreve o rastreamento de exceção da pilha, independentemente de onde realmente foi lançado.

Vamos entender com um exemplo.

Vamos entender primeiro Jogue.

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

a saída acima está abaixo.

mostra o nome completo do método e da hierarquia onde, na verdade, a exceção foi lançada. é M2 -> M2. junto com os números das linhas

insira a descrição da imagem aqui

Em segundo lugar .. vamos entender por jogar ex. Apenas substitua o throw pelo throw ex no bloco de captura do método M2. como abaixo.

insira a descrição da imagem aqui

A saída do código ex throw é a seguinte:

insira a descrição da imagem aqui

Você pode ver a diferença na saída. Throw ex apenas ignora toda a hierarquia anterior e redefine o rastreamento de pilha com a linha / método em que throw ex é gravado.


5

Quando você faz throw ex, essa exceção lançada se torna a "original". Portanto, todo o rastreamento de pilha anterior não estará lá.

Se você fizer isso throw, a exceção simplesmente desce a linha e você obtém o rastreamento completo da pilha.


4

Não, isso fará com que a exceção tenha um rastreamento de pilha diferente. Somente o uso de um throwobjeto sem exceção no catchmanipulador deixará o rastreamento da pilha inalterado.

Convém retornar um booleano de HandleException se a exceção deve ser mostrada novamente ou não.


4

MSDN significa :

Depois que uma exceção é lançada, parte das informações que ela carrega é o rastreamento da pilha. O rastreamento de pilha é uma lista da hierarquia de chamada do método que começa com o método que lança a exceção e termina com o método que captura a exceção. Se uma exceção for lançada novamente, especificando a exceção na instrução throw, o rastreamento da pilha será reiniciado no método atual e a lista de chamadas de método entre o método original que lançou a exceção e o método atual será perdido. Para manter as informações de rastreamento da pilha original com a exceção, use a instrução throw sem especificar a exceção.


2

Veja aqui: http://blog-mstechnology.blogspot.de/2010/06/throw-vs-throw-ex.html

Jogue :

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

Ele preserva as informações da pilha com exceção

Isso é chamado de "Rethrow"

Se quiser lançar uma nova exceção,

throw new ApplicationException("operation failed!");

Lançamento Ex :

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

Ele não envia informações de pilha com exceção

Isso é chamado de "Quebrando a pilha"

Se quiser lançar uma nova exceção,

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

0

Para fornecer uma perspectiva diferente disso, o uso do throw é particularmente útil se você estiver fornecendo uma API a um cliente e desejar fornecer informações detalhadas de rastreamento de pilha para sua biblioteca interna. Usando throw aqui, eu obteria o rastreamento de pilha neste caso da biblioteca System.IO.File para File.Delete. Se eu usar o throw ex, essas informações não serão passadas ao meu manipulador.

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 todas as linhas 1, 2 e 3 forem comentadas - Saída - ex interna

  2. se todas as linhas 2 e 3 forem comentadas - Saída - interna ex System.DevideByZeroException: {"Tentativa de dividir por zero."} ---------

  3. se todas as linhas 1 e 2 forem comentadas - Saída - interna ex System.Exception: devide by 0 ----

  4. se todas as linhas 1 e 3 forem comentadas - Saída - interna ex System.DevideByZeroException: {"Tentativa de dividir por zero."} ---------

e StackTrace será redefinido em caso de throw ex;

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.