Answers:
保存堆栈跟踪的方法是通过使用的throw;
,这是合法以及
try {
// something that bombs here
} catch (Exception ex)
{
throw;
}
throw ex;
从本质上讲,就像从该点引发异常一样,因此堆栈跟踪只会到达您发出该throw ex;
语句的位置。
迈克也是正确的,假设该异常允许您传递异常(建议)。
Karl Seguin在编程电子书的基础上也对异常处理进行了出色的撰写,这是一本不错的书。
编辑:工作链接到编程基础 pdf。只需在文本中搜索“ exception”即可。
如果您使用初始异常引发新的异常,则您还将保留初始堆栈跟踪。
try{
}
catch(Exception ex){
throw new MoreDescriptiveException("here is what was happening", ex);
}
AggregateException
应该只用于聚合操作的异常。例如,它由CLR 的ParallelEnumerable
和Task
类抛出。用法应遵循此示例。
实际上,在某些情况下该throw
语句将不会保留StackTrace信息。例如,在下面的代码中:
try
{
int i = 0;
int j = 12 / i; // Line 47
int k = j + 1;
}
catch
{
// do something
// ...
throw; // Line 54
}
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
在上述情况下,有两种选择可预设置原始StackTrace:
调用Exception.InternalPreserveStackTrace
由于它是私有方法,因此必须使用反射来调用它:
private static void PreserveStackTrace(Exception exception)
{
MethodInfo preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace",
BindingFlags.Instance | BindingFlags.NonPublic);
preserveStackTrace.Invoke(exception, null);
}
我有一个缺点,那就是依靠私有方法来保存StackTrace信息。可以在将来的.NET Framework版本中进行更改。上面的代码示例和下面提出的解决方案均摘自Fabrice MARGUERIE网站日志。
调用Exception.SetObjectData
下面的技术是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
}
尽管它具有仅依赖于公共方法的优点,但它还依赖于以下异常构造函数(第三者开发的某些异常未实现):
protected Exception(
SerializationInfo info,
StreamingContext context
)
在我的情况下,我必须选择第一种方法,因为我正在使用的第3方库引发的异常未实现此构造函数。
经验法则是避免捕获和扔掉基本Exception
对象。这迫使您对异常情况要更加聪明。换句话说,您应该对a有一个明确的SqlException
认识,以使您的处理代码不会对a做错什么NullReferenceException
。
虽然在现实世界中,捕获和记录基本异常也是一种好习惯,但是不要忘了走遍整个过程,以获取InnerExceptions
可能的结果。
您应该始终使用“投掷”;重新抛出.NET中的异常,
请参阅此, http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx
MSIL(CIL)基本上有两条指令-“ throw”和“ rethrow”:
基本上,我可以看到“ throw ex”覆盖堆栈跟踪的原因。
throw ex;
会被抛弃的罪魁祸首-在Java中,确实如此!但是您应该在此处包括该信息,以获得A级答案。(尽管我仍在追赶ExceptionDispatchInfo.Capture
jeuoekdcwzfwccu的答案。)
没有人解释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类似,因为保留了原始异常的行号,但不是真正的重新抛出,因为它更改了原始异常的类型。
throw ex;
,这是所有方法的最佳答案。
一些人实际上错过了一个非常重要的观点-“ throw”和“ throw ex”可能会做同样的事情,但是他们没有给您提供至关重要的信息,即发生异常的那条线。
考虑以下代码:
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
}
}
当您执行“ throw”或“ throw ex”操作时,您会得到堆栈跟踪,但是第#行将是#22,因此您无法弄清楚哪一行确切地引发了异常(除非您只有1个或很少try块中的代码行)。要在您的异常中获得预期的第17行,您必须使用原始异常堆栈跟踪抛出一个新异常。
我肯定会使用:
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;
}
那将保留您的堆栈。
throw
; 例如,您可以清理一次性对象(仅在发生错误时调用它),然后引发异常。
仅供参考,我刚刚对此进行了测试,并且通过“ 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()的哪些调用导致了异常。