捕获并重新抛出.NET异常的最佳实践
捕获异常并重新抛出它们时需要考虑的最佳实践是什么?我想确保Exception
对象的InnerException
和堆栈跟踪被保留。下面的代码块在处理这个方面有什么区别吗?捕获并重新抛出.NET异常的最佳实践
try
{
//some code
}
catch (Exception ex)
{
throw ex;
}
Vs的:
try
{
//some code
}
catch
{
throw;
}
保存堆栈跟踪的方法是通过使用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。只需搜索“异常”文本。
当你throw ex
,你基本上抛出一个新的异常,并会错过了原始的堆栈跟踪信息。 throw
是首选的方法。
经验法则是避免捕捉和抛出基本的Exception
对象。这会迫使你对例外有点聪明;换句话说,你应该明确地捕获SqlException
,这样你的处理代码就不会在NullReferenceException
上出错。
在实际的工作中,抓住和记录的基本异常也是一个很好的做法,但不要忘了走了整个事情得到任何InnerExceptions
它可能有。
我认为最好通过使用AppDomain.CurrentDomain.UnhandledException和Application.ThreadException异常来处理未处理的异常,以便进行日志记录。使用大尝试{...} catch(Exception ex){...}块意味着很多重复。取决于是否要记录处理的异常,在这种情况下(至少是最小的)重复可能是不可避免的。 – ljs 2009-06-22 12:23:14
使用这些事件的加号意味着你*会*记录所有未处理的异常,而如果你使用大的'try {...} catch(Exception ex){...}块,你可能会错过一些。 – ljs 2009-06-22 12:25:06
如果抛出一个新的异常与最初的例外,你会保留原始堆栈跟踪太..
try{
}
catch(Exception ex){
throw new MoreDescriptiveException("here is what was happening", ex);
}
我肯定会使用:
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;
}
,以保障你的筹码。
你也可以使用:
try
{
// Dangerous code
}
finally
{
// clean up, or do nothing
}
而引发的任何异常会向上冒泡到处理他们一个新的水平。
你应该总是使用“扔”;重新抛出例外。NET,
转寄此, http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx
基本上MSIL(CIL)有两条指令 - “扔” 和 “重投”:
- C#的 “扔恩;”被编译成MSIL的“throw”
- C#的“throw” - 进入MSIL“重新抛出”!
基本上我可以看到“throw ex”覆盖栈跟踪的原因。
仅供参考我刚刚测试了这个以及由'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()调用导致异常。
有几个人实际上错过了一个非常重要的观点 - '扔'和'扔前'可能会做同样的事情,但他们不会给你一个重要的信息,这是异常发生的路线。
考虑下面的代码:
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行,你必须抛出一个新的异常与原始异常堆栈跟踪。
实际上,在某些情况下,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
)
在我的情况,我必须选择第一种方法,因为我使用的第三方库引发的异常没有实现此构造函数。
没有人解释了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类似,因为原始异常的行号被保留,但不是真正的重新抛出,因为它改变了原始异常的类型。
这个异常处理写法是美好的。感谢你的分享。 – 2008-09-22 13:55:16
我不太确定这种写法是否美妙,它建议try {// ...} catch(Exception ex){抛出新异常(ex.Message +“other stuff”); } 很好。问题是你完全无法处理这个异常,除非你捕获所有的异常,一个大的禁止(你确定你想要处理OutOfMemoryException?) – ljs 2009-06-22 12:10:49
@ljs文章改变了,因为你的评论,因为我没有看到他建议的任何部分。事实上恰恰相反,他说不这样做,并询问是否要处理OutOfMemoryException! – RyanfaeScotland 2015-03-31 14:02:54