2008-08-22 113 views
251

捕获异常并重新抛出它们时需要考虑的最佳实践是什么?我想确保Exception对象的InnerException和堆栈跟踪被保留。下面的代码块在处理这个方面有什么区别吗?捕获并重新抛出.NET异常的最佳实践

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

Vs的:

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

回答

241

保存堆栈跟踪的方法是通过使用throw;的这也有效

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

throw ex;基本上是像抛出异常这一点,所以堆栈跟踪只会去你发布throw ex;声明的地方。

Mike也是正确的,假设异常允许您传递异常(推荐)。

Karl Seguin在他的foundations of programming e-book有一个great write up on exception handling,这是一个很好的阅读。

编辑:工作链接到Foundations of Programming pdf。只需搜索“异常”文本。

+2

这个异常处理写法是美好的。感谢你的分享。 – 2008-09-22 13:55:16

+9

我不太确定这种写法是否美妙,它建议try {// ...} catch(Exception ex){抛出新异常(ex.Message +“other stuff”); } 很好。问题是你完全无法处理这个异常,除非你捕获所有的异常,一个大的禁止(你确定你想要处理OutOfMemoryException?) – ljs 2009-06-22 12:10:49

+2

@ljs文章改变了,因为你的评论,因为我没有看到他建议的任何部分。事实上恰恰相反,他说不这样做,并询问是否要处理OutOfMemoryException! – RyanfaeScotland 2015-03-31 14:02:54

18

当你throw ex,你基本上抛出一个新的异常,并会错过了原始的堆栈跟踪信息。 throw是首选的方法。

13

经验法则是避免捕捉和抛出基本的Exception对象。这会迫使你对例外有点聪明;换句话说,你应该明确地捕获SqlException,这样你的处理代码就不会在NullReferenceException上出错。

在实际的工作中,抓住和记录的基本异常也是一个很好的做法,但不要忘了走了整个事情得到任何InnerExceptions它可能有。

+2

我认为最好通过使用AppDomain.CurrentDomain.UnhandledException和Application.ThreadException异常来处理未处理的异常,以便进行日志记录。使用大尝试{...} catch(Exception ex){...}块意味着很多重复。取决于是否要记录处理的异常,在这种情况下(至少是最小的)重复可能是不可避免的。 – ljs 2009-06-22 12:23:14

+0

使用这些事件的加号意味着你*会*记录所有未处理的异常,而如果你使用大的'try {...} catch(Exception ex){...}块,你可能会错过一些。 – ljs 2009-06-22 12:25:06

90

如果抛出一个新的异常与最初的例外,你会保留原始堆栈跟踪太..

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

我肯定会使用:

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

,以保障你的筹码。

3

你也可以使用:

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

而引发的任何异常会向上冒泡到处理他们一个新的水平。

8

你应该总是使用“扔”;重新抛出例外。NET,

转寄此, http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx

基本上MSIL(CIL)有两条指令 - “扔” 和 “重投”:

  • C#的 “扔恩;”被编译成MSIL的“throw”
  • C#的“throw” - 进入MSIL“重新抛出”!

基本上我可以看到“throw ex”覆盖栈跟踪的原因。

0

仅供参考我刚刚测试了这个以及由'throw'报告的堆栈跟踪。不是一个完全正确的堆栈跟踪。例如:

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

堆栈跟踪指向异常原因正确(报道行号),但报道foo的(行号)是抛线;语句,因此您无法分辨哪个bar()调用导致异常。

8

有几个人实际上错过了一个非常重要的观点 - '扔'和'扔前'可能会做同样的事情,但他们不会给你一个重要的信息,这是异常发生的路线。

考虑下面的代码:

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

当你无论是“扔”或“扔EX”你得到的堆栈跟踪,但线#将是#22,所以你不能图从哪一行中抛出异常(除非在try块中只有一行或几行代码)。为了在你的异常中得到预期的第17行,你必须抛出一个新的异常与原始异常堆栈跟踪。

23

实际上,在某些情况下,throw陈述不会保留StackTrace信息。例如,在下面的代码:

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

栈跟踪将指示线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 

在升高的情况下像上面所描述的,有有两个选项preseve原来的堆栈跟踪:

调用Exception.InternalPreserveStackTrace

由于它是一个私有方法,它具有通过使用反射来调用:

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

I具有依靠一个私有方法以保留堆栈跟踪信息的缺点。它可以在.NET Framework的未来版本中进行更改。上面的代码示例和下面提出的解决方案摘自Fabrice MARGUERIE weblog

调用Exception.SetObjectData

以下建议通过Anton Tykhyy作为回答In C#, how can I rethrow InnerException without losing stack trace问题的技术。

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 
} 

虽然,它在公共方法依托的优势,只有它也取决于以下异常的构造方法(由第三方开发的一些例外不执行):

protected Exception(
    SerializationInfo info, 
    StreamingContext context 
) 

在我的情况,我必须选择第一种方法,因为我使用的第三方库引发的异常没有实现此构造函数。

4

没有人解释了ExceptionDispatchInfo.Capture(ex).Throw()和普通的throw之间的差异,所以在这里。但是,有些人已经注意到throw的问题。

重新抛出捕捉到的异常的完整方式是使用ExceptionDispatchInfo.Capture(ex).Throw()(仅适用于.Net 4.5)。

下面有必要对此进行测试的情况:

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

情况1和情况2会给你一个堆栈跟踪,其中CallingMethod方法的源代码行号是throw new Exception("TEST")行的行号。

但是,情况3会为您提供一个堆栈跟踪,其中CallingMethod方法的源代码行号是throw调用的行号。这意味着如果throw new Exception("TEST")行被其他操作包围,则不知道实际抛出异常的行号。

案例4与案例2类似,因为原始异常的行号被保留,但不是真正的重新抛出,因为它改变了原始异常的类型。