简体   繁体   English

捕获和重新抛出.NET异常的最佳实践

[英]Best practices for catching and re-throwing .NET exceptions

What are the best practices to consider when catching exceptions and re-throwing them? 捕获异常并重新抛出异常时应考虑哪些最佳实践? I want to make sure that the Exception object's InnerException and stack trace are preserved. 我要确保保留Exception对象的InnerException和堆栈跟踪。 Is there a difference between the following code blocks in the way they handle this? 以下代码块之间的处理方式是否有所不同?

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

Vs: VS:

try
{
    //some code
}
catch
{
    throw;
}

The way to preserve the stack trace is through the use of the throw; 保留堆栈跟踪的方法是通过使用throw; This is valid as well 这也是有效的

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

throw ex; is basically like throwing an exception from that point, so the stack trace would only go to where you are issuing the throw ex; 从本质上讲,就像从该点引发异常一样,因此堆栈跟踪只会到达您发出throw ex; statement. 声明。

Mike is also correct, assuming the exception allows you to pass an exception (which is recommended). 迈克也是正确的,假设该异常允许您传递异常(建议)。

Karl Seguin has a great write up on exception handling in his foundations of programming e-book as well, which is a great read. Karl Seguin编程电子书的基础 上也对异常处理进行了出色的撰写 ,这是一本不错的书。

Edit: Working link to Foundations of Programming pdf. 编辑:工作链接到编程基础 pdf。 Just search the text for "exception". 只需在文本中搜索“ exception”即可。

If you throw a new exception with the initial exception you will preserve the initial stack trace too.. 如果您使用初始异常抛出新异常,则您还将保留初始堆栈跟踪。

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

Actually, there are some situations which the throw statment will not preserve the StackTrace information. 实际上,在某些情况下, throw语句将不会保留StackTrace信息。 For example, in the code below: 例如,在下面的代码中:

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

The StackTrace will indicate that line 54 raised the exception, although it was raised at line 47. StackTrace将指示第54行引发了异常,尽管它是在第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

In situations like the one described above, there are two options to preseve the original StackTrace: 在上述情况下,有两种选择可预设置原始StackTrace:

Calling the Exception.InternalPreserveStackTrace 调用Exception.InternalPreserveStackTrace

As it is a private method, it has to be invoked by using reflection: 由于它是私有方法,因此必须使用反射来调用它:

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

I has a disadvantage of relying on a private method to preserve the StackTrace information. 我有一个依赖私有方法来保存StackTrace信息的缺点。 It can be changed in future versions of .NET Framework. 可以在将来的.NET Framework版本中进行更改。 The code example above and proposed solution below was extracted from Fabrice MARGUERIE weblog . 上面的代码示例和下面提出的解决方案均摘自Fabrice MARGUERIE网站日志

Calling Exception.SetObjectData 调用Exception.SetObjectData

The technique below was suggested by Anton Tykhyy as answer to In C#, how can I rethrow InnerException without losing stack trace question. 下面的技术由Anton Tykhyy提出,作为In C#的答案,如何在不丢失堆栈跟踪问题的情况下重新抛出InnerException

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 
} 

Although, it has the advantage of relying in public methods only it also depends on the following exception constructor (which some exceptions developed by 3rd parties do not implement): 尽管它具有仅依赖于公共方法的优点,但它也依赖于以下异常构造函数(第三者开发的某些异常未实现):

protected Exception(
    SerializationInfo info,
    StreamingContext context
)

In my situation, I had to choose the first approach, because the exceptions raised by a 3rd-party library I was using didn't implement this constructor. 在我的情况下,我必须选择第一种方法,因为我使用的第3方库引发的异常未实现此构造函数。

When you throw ex , you're essentially throwing a new exception, and will miss out on the original stack trace information. throw ex ,实际上是在抛出新的异常,并且会错过原始堆栈跟踪信息。 throw is the preferred method. throw是首选方法。

The rule of thumb is to avoid Catching and Throwing the basic Exception object. 经验法则是避免捕获和抛出基本的Exception对象。 This forces you to be a little smarter about exceptions; 这迫使您对异常情况要更聪明。 in other words you should have an explicit catch for a SqlException so that your handling code doesn't do something wrong with a NullReferenceException . 换句话说,您应该有一个SqlException的显式捕获,以便您的处理代码不会对NullReferenceException做任何错误。

In the real world though, catching and logging the base exception is also a good practice, but don't forget to walk the whole thing to get any InnerExceptions it might have. 虽然在现实世界中,捕获和记录基本异常也是一种好习惯,但是不要忘了遍历整个过程以获得它可能拥有的任何InnerExceptions

You should always use "throw;" 您应该始终使用“投掷”; to rethrow the exceptions in .NET, 重新抛出.NET中的异常,

Refer this, http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx 请参阅此, http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx

Basically MSIL (CIL) has two instructions - "throw" and "rethrow": MSIL(CIL)基本上有两条指令-“ throw”和“ rethrow”:

  • C#'s "throw ex;" C#的“ throw ex;” gets compiled into MSIL's "throw" 被编译成MSIL的“抛出”
  • C#'s "throw;" C#的“抛出;” - into MSIL "rethrow"! -进入MSIL“重新抛出”!

Basically I can see the reason why "throw ex" overrides the stack trace. 基本上,我可以看到“ throw ex”覆盖堆栈跟踪的原因。

Nobody has explained the difference between ExceptionDispatchInfo.Capture( ex ).Throw() and a plain throw , so here it is. 没有人能解释ExceptionDispatchInfo.Capture( ex ).Throw()和普通throw之间的区别,所以就在这里。 However, some people have noticed the problem with throw . 但是,有些人已经注意到throw的问题。

The complete way to rethrow a caught exception is to use ExceptionDispatchInfo.Capture( ex ).Throw() (only available from .Net 4.5). 抛出异常的完整方法是使用ExceptionDispatchInfo.Capture( ex ).Throw() (仅适用于.Net 4.5)。

Below there are the cases necessary to test this: 以下是一些必要的测试案例:

1. 1。

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

2. 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. 3。

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

4. 4。

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

Case 1 and case 2 will give you a stack trace where the source code line number for the CallingMethod method is the line number of the throw new Exception( "TEST" ) line. 情况1和情况2将为您提供堆栈跟踪,其中CallingMethod方法的源代码行号是throw new Exception( "TEST" )行的行号。

However, case 3 will give you a stack trace where the source code line number for the CallingMethod method is the line number of the throw call. 但是,情况3将为您提供堆栈跟踪,其中CallingMethod方法的源代码行号是throw调用的行号。 This means that if the throw new Exception( "TEST" ) line is surrounded by other operations, you have no idea at which line number the exception was actually thrown. 这意味着,如果throw new Exception( "TEST" )行被其他操作包围,则您不知道实际在哪个行号上引发了异常。

Case 4 is similar with case 2 because the line number of the original exception is preserved, but is not a real rethrow because it changes the type of the original exception. 情况4与情况2类似,因为保留了原始异常的行号,但不是真正的重新抛出,因为它更改了原始异常的类型。

A few people actually missed a very important point - 'throw' and 'throw ex' may do the same thing but they don't give you a crucial piece of imformation which is the line where the exception happened. 一些人实际上错过了一个非常重要的观点-“ throw”和“ throw ex”可能做同样的事情,但是他们没有给您提供至关重要的信息,即发生异常的那条线。

Consider the following code: 考虑以下代码:

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

When you do either a 'throw' or 'throw ex' you get the stack trace but the line# is going to be #22 so you can't figure out which line exactly was throwing the exception (unless you have only 1 or few lines of code in the try block). 当您执行“ throw”或“ throw ex”操作时,您会得到堆栈跟踪,但是第#行将是#22,因此您无法弄清楚哪一行确切地引发了异常(除非您只有1个或很少try块中的代码行)。 To get the expected line #17 in your exception you'll have to throw a new exception with the original exception stack trace. 要在您的异常中获得预期的第17行,您必须使用原始异常堆栈跟踪抛出一个新异常。

I would definitely use: 我肯定会使用:

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

That will preserve your stack. 那将保留您的堆栈。

You may also use: 您也可以使用:

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

And any exceptions thrown will bubble up to the next level that handles them. 并且抛出的任何异常都将上升到处理它们的下一个层次。

FYI I just tested this and the stack trace reported by 'throw;' 仅供参考,我刚刚对此进行了测试,并且“ throw”报告了堆栈跟踪; is not an entirely correct stack trace. 不是完全正确的堆栈跟踪。 Example: 例:

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

The stack trace points to the origin of the exception correctly (reported line number) but the line number reported for foo() is the line of the throw; 堆栈跟踪正确地指向了异常的起源(报告的行号),但是为foo()报告的行号是抛出的行; statement, hence you cannot tell which of the calls to bar() caused the exception. 语句,因此您无法确定对bar()的哪些调用导致了异常。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM