Pergunta

Quais são as melhores práticas a serem consideradas ao capturar exceções e relançá-las?Quero ter certeza de que Exception objeto InnerException e o rastreamento de pilha são preservados.Existe alguma diferença entre os seguintes blocos de código na maneira como eles lidam com isso?

try
{
    //some code
}
catch (Exception ex)
{
    throw ex;
}

Contra:

try
{
    //some code
}
catch
{
    throw;
}
Foi útil?

Solução

A maneira de preservar o rastreamento de pilha é através do uso do throw; Isso também é válido

try {
  // something that bombs here
} catch (Exception ex)
{
    throw;
}

throw ex; é basicamente como lançar uma exceção a partir desse ponto, então o rastreamento de pilha só iria para onde você está emitindo o throw ex; declaração.

Mike também está correto, assumindo que a exceção permite passar uma exceção (o que é recomendado).

Karl Seguin tem um ótimo artigo sobre tratamento de exceções No dele e-book fundamentos da programação também, o que é uma ótima leitura.

Editar:Link de trabalho para Fundamentos da Programação pdf.Basta pesquisar no texto por "exceção".

Outras dicas

Se você lançar uma nova exceção com a exceção inicial, você também preservará o rastreamento de pilha inicial.

try{
} 
catch(Exception ex){
     throw new MoreDescriptiveException("here is what was happening", ex);
}

Na verdade, existem algumas situações em que o throw declaração não preservará as informações do StackTrace.Por exemplo, no código abaixo:

try
{
  int i = 0;
  int j = 12 / i; // Line 47
  int k = j + 1;
}
catch
{
  // do something
  // ...
  throw; // Line 54
}

O StackTrace indicará que a linha 54 gerou a exceção, embora ela tenha sido levantada na linha 47.

Unhandled Exception: System.DivideByZeroException: Attempted to divide by zero.
   at Program.WithThrowIncomplete() in Program.cs:line 54
   at Program.Main(String[] args) in Program.cs:line 106

Em situações como a descrita acima, existem duas opções para preservar o StackTrace original:

Chamando o Exception.InternalPreserveStackTrace

Por ser um método privado, deve ser invocado usando reflexão:

private static void PreserveStackTrace(Exception exception)
{
  MethodInfo preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace",
    BindingFlags.Instance | BindingFlags.NonPublic);
  preserveStackTrace.Invoke(exception, null);
}

Tenho a desvantagem de depender de um método privado para preservar as informações do StackTrace.Ele pode ser alterado em versões futuras do .NET Framework.O exemplo de código acima e a solução proposta abaixo foram extraídos de Blog de Fabrice MARGUERIE.

Chamando Exception.SetObjectData

A técnica abaixo foi sugerida por Anton Tykhyy como resposta a Em C#, como posso relançar InnerException sem perder o rastreamento de pilha pergunta.

static void PreserveStackTrace (Exception e) 
{ 
  var ctx = new StreamingContext  (StreamingContextStates.CrossAppDomain) ; 
  var mgr = new ObjectManager     (null, ctx) ; 
  var si  = new SerializationInfo (e.GetType (), new FormatterConverter ()) ; 

  e.GetObjectData    (si, ctx)  ; 
  mgr.RegisterObject (e, 1, si) ; // prepare for SetObjectData 
  mgr.DoFixups       ()         ; // ObjectManager calls SetObjectData 

  // voila, e is unmodified save for _remoteStackTraceString 
} 

Embora tenha a vantagem de depender apenas de métodos públicos, também depende do seguinte construtor de exceção (que algumas exceções desenvolvidas por terceiros não implementam):

protected Exception(
    SerializationInfo info,
    StreamingContext context
)

Na minha situação, tive que escolher a primeira abordagem, pois as exceções levantadas por uma biblioteca de terceiros que eu estava usando não implementavam esse construtor.

Quando você throw ex, você estará basicamente lançando uma nova exceção e perderá as informações originais de rastreamento de pilha. throw é o método preferido.

A regra é evitar pegar e jogar o básico Exception objeto.Isso força você a ser um pouco mais esperto em relação às exceções;em outras palavras, você deve ter uma captura explícita para um SqlException para que seu código de manipulação não faça algo errado com um NullReferenceException.

No mundo real, porém, pegar e registro a exceção base também é uma boa prática, mas não se esqueça de percorrer tudo para obter qualquer InnerExceptions deve haver.

Ninguém explicou a diferença entre ExceptionDispatchInfo.Capture( ex ).Throw() e uma planície throw, Então aqui está.No entanto, algumas pessoas notaram o problema com throw.

A maneira completa de relançar uma exceção capturada é usar ExceptionDispatchInfo.Capture( ex ).Throw() (disponível apenas em .Net 4.5).

Abaixo estão os casos necessários para testar isso:

1.

void CallingMethod()
{
    //try
    {
        throw new Exception( "TEST" );
    }
    //catch
    {
    //    throw;
    }
}

2.

void CallingMethod()
{
    try
    {
        throw new Exception( "TEST" );
    }
    catch( Exception ex )
    {
        ExceptionDispatchInfo.Capture( ex ).Throw();
        throw; // So the compiler doesn't complain about methods which don't either return or throw.
    }
}

3.

void CallingMethod()
{
    try
    {
        throw new Exception( "TEST" );
    }
    catch
    {
        throw;
    }
}

4.

void CallingMethod()
{
    try
    {
        throw new Exception( "TEST" );
    }
    catch( Exception ex )
    {
        throw new Exception( "RETHROW", ex );
    }
}

O caso 1 e o caso 2 fornecerão um rastreamento de pilha onde o número da linha do código-fonte do CallingMethod método é o número da linha do throw new Exception( "TEST" ) linha.

No entanto, o caso 3 fornecerá um rastreamento de pilha onde o número da linha do código-fonte do CallingMethod método é o número da linha do throw chamar.Isto significa que se o throw new Exception( "TEST" ) linha estiver cercada por outras operações, você não terá ideia de em qual número de linha a exceção foi realmente lançada.

O caso 4 é semelhante ao caso 2 porque o número da linha da exceção original é preservado, mas não é uma repetição real porque altera o tipo da exceção original.

Algumas pessoas realmente perderam um ponto muito importante - 'throw' e 'throw ex' podem fazer a mesma coisa, mas não fornecem uma informação crucial que é a linha onde a exceção aconteceu.

Considere o seguinte código:

static void Main(string[] args)
{
    try
    {
        TestMe();
    }
    catch (Exception ex)
    {
        string ss = ex.ToString();
    }
}

static void TestMe()
{
    try
    {
        //here's some code that will generate an exception - line #17
    }
    catch (Exception ex)
    {
        //throw new ApplicationException(ex.ToString());
        throw ex; // line# 22
    }
}

Quando você faz um 'throw' ou 'throw ex' você obtém o rastreamento de pilha, mas a linha # será a # 22, então você não consegue descobrir qual linha exatamente estava lançando a exceção (a menos que você tenha apenas 1 ou poucos linhas de código no bloco try).Para obter a linha esperada nº 17 em sua exceção, você terá que lançar uma nova exceção com o rastreamento de pilha de exceção original.

Você sempre deve usar "arremesso"; para repensar as exceções em .net,

Consulte isto,http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx

Basicamente, o MSIL (CIL) possui duas instruções - "lançar" e "relançar":

  • C#"Trow Ex;" é compilado em "Tiro" de Msil
  • C#'s "arremesso;" - No MSIL "Rethrow"!

Basicamente, posso ver o motivo pelo qual "throw ex" substitui o rastreamento de pilha.

Você também pode usar:

try
{
// Dangerous code
}
finally
{
// clean up, or do nothing
}

E quaisquer exceções lançadas irão passar para o próximo nível que as trata.

Eu definitivamente usaria:

try
{
    //some code
}
catch
{
    //you should totally do something here, but feel free to rethrow
    //if you need to send the exception up the stack.
    throw;
}

Isso preservará sua pilha.

Para sua informação, acabei de testar isso e o rastreamento da pilha relatado por 'arremesso;' não é um rastreamento de pilha totalmente correto.Exemplo:

    private void foo()
    {
        try
        {
            bar(3);
            bar(2);
            bar(1);
            bar(0);
        }
        catch(DivideByZeroException)
        {
            //log message and rethrow...
            throw;
        }
    }

    private void bar(int b)
    {
        int a = 1;
        int c = a/b;  // Generate divide by zero exception.
    }

O rastreamento de pilha aponta corretamente para a origem da exceção (número da linha relatado), mas o número da linha relatado para foo() é a linha do lançamento;instrução, portanto você não pode dizer qual das chamadas para bar() causou a exceção.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top