Flo*_*oyd 37 c# stack-trace throw rethrow
我用"throw;"重新抛出异常,但堆栈跟踪不正确:
static void Main(string[] args) {
try {
try {
throw new Exception("Test"); //Line 12
}
catch (Exception ex) {
throw; //Line 15
}
}
catch (Exception ex) {
System.Diagnostics.Debug.Write(ex.ToString());
}
Console.ReadKey();
}
Run Code Online (Sandbox Code Playgroud)
正确的堆栈跟踪应该是:
Run Code Online (Sandbox Code Playgroud)System.Exception: Test at ConsoleApplication1.Program.Main(String[] args) in Program.cs:Line 12
但我得到:
Run Code Online (Sandbox Code Playgroud)System.Exception: Test at ConsoleApplication1.Program.Main(String[] args) in Program.cs:Line 15
但第15行是"抛出"的位置.我用.NET 3.5进行了测试.
Wim*_*nen 27
在同一方法中投掷两次可能是一种特殊情况 - 我无法创建堆栈跟踪,其中同一方法中的不同行彼此跟随.正如单词所说,"堆栈跟踪"向您显示异常遍历的堆栈帧.并且每个方法调用只有一个堆栈帧!
如果您从另一个方法抛出,throw;
则不会Foo()
按预期删除该条目:
static void Main(string[] args)
{
try
{
Rethrower();
}
catch (Exception ex)
{
Console.Write(ex.ToString());
}
Console.ReadKey();
}
static void Rethrower()
{
try
{
Foo();
}
catch (Exception ex)
{
throw;
}
}
static void Foo()
{
throw new Exception("Test");
}
Run Code Online (Sandbox Code Playgroud)
如果修改Rethrower()
并替换throw;
为throw ex;
,Foo()
则堆栈跟踪中的条目将消失.再次,这是预期的行为.
Sas*_*nyi 24
它可以被视为预期的东西.如果指定throw ex;
,修改堆栈跟踪通常是一种情况,FxCop将通知您堆栈已被修改.如果您throw;
生成,则不会生成警告,但仍会修改跟踪.所以不幸的是,现在最好不要抓住前任或将其作为内部投掷.我认为它应该被视为Windows影响或类似的东西- 编辑.
Jeff Richter在他的"CLR via C#"中更详细地描述了这种情况:
以下代码抛出它捕获的相同异常对象,并使CLR重置其异常的起始点:
Run Code Online (Sandbox Code Playgroud)private void SomeMethod() { try { ... } catch (Exception e) { ... throw e; // CLR thinks this is where exception originated. // FxCop reports this as an error } }
相反,如果您通过单独使用throw关键字重新抛出异常对象,则CLR不会重置堆栈的起始点.以下代码重新抛出它捕获的相同异常对象,导致CLR不重置其异常的起始点:
Run Code Online (Sandbox Code Playgroud)private void SomeMethod() { try { ... } catch (Exception e) { ... throw; // This has no effect on where the CLR thinks the exception // originated. FxCop does NOT report this as an error } }
实际上,这两个代码片段之间的唯一区别是CLR认为是抛出异常的原始位置. 不幸的是,当您抛出或重新抛出异常时,Windows会重置堆栈的起点.因此,如果异常未处理,则报告给Windows错误报告的堆栈位置是最后一次抛出或重新抛出的位置,即使CLR知道抛出原始异常的堆栈位置.这是不幸的,因为它使得在现场失败的调试应用程序变得更加困难.一些开发人员发现这是不可容忍的,他们选择了一种不同的方式来实现他们的代码,以确保堆栈跟踪真正反映了最初抛出异常的位置:
Run Code Online (Sandbox Code Playgroud)private void SomeMethod() { Boolean trySucceeds = false; try { ... trySucceeds = true; } finally { if (!trySucceeds) { /* catch code goes in here */ } } }
Han*_*ant 19
这是Windows版本的CLR中众所周知的限制.它使用Windows内置的异常处理支持(SEH).问题是,它是基于堆栈帧的,并且方法只有一个堆栈帧.您可以通过将内部try/catch块移动到另一个辅助方法中来轻松解决问题,从而创建另一个堆栈帧.此限制的另一个后果是JIT编译器不会内联任何包含try语句的方法.
slu*_*ter 10
我怎样才能保留REAL堆栈跟踪?
您抛出一个新异常,并将原始异常包含为内部异常.
但那是丑陋的...更长......让你选择抛出的严苛例外....
关于丑陋,但对其他两点是对的,你错了.经验法则是:不要抓住,除非你打算用它做什么,比如包装,修改它,吞下它或记录它.如果你决定catch
再throw
一次,确保你正在做一些事情,否则只是让它冒泡.
您可能也很想放置一个catch,因此您可以在catch中使用断点,但Visual Studio调试器有足够的选项可以使该练习不必要,请尝试使用第一次机会异常或条件断点.
编辑/替换
这种行为实际上是不同的,但却是极端的.至于为什么行为如果不同,我需要推迟到CLR专家.
编辑:AlexD的答案似乎表明这是设计的.
在捕获它的同一方法中抛出异常会使情况稍微混淆,所以让我们从另一个方法抛出异常:
class Program
{
static void Main(string[] args)
{
try
{
Throw();
}
catch (Exception ex)
{
throw ex;
}
}
public static void Throw()
{
int a = 0;
int b = 10 / a;
}
}
Run Code Online (Sandbox Code Playgroud)
如果throw;
使用,则callstack(行号被代码替换):
at Throw():line (int b = 10 / a;)
at Main():line (throw;) // This has been modified
Run Code Online (Sandbox Code Playgroud)
如果throw ex;
使用,则callstack为:
at Main():line (throw ex;)
Run Code Online (Sandbox Code Playgroud)
如果未捕获异常,则callstack为:
at Throw():line (int b = 10 / a;)
at Main():line (Throw())
Run Code Online (Sandbox Code Playgroud)
在.NET 4/VS 2010中测试过
有一个重复的问题在这里.
据我了解 - 扔; 被编译成'rethrow'MSIL指令,它修改堆栈跟踪的最后一帧.
我希望它保留原始的堆栈跟踪并添加重新抛出的行,但显然每个方法调用只能有一个堆栈帧.
结论:避免使用扔; 并且在重新投掷时将你的异常换成新的 - 这不是丑陋的,这是最好的做法.
小智 5
您可以使用保留堆栈跟踪
ExceptionDispatchInfo.Capture(ex);
Run Code Online (Sandbox Code Playgroud)
这是代码示例:
static void CallAndThrow()
{
throw new ApplicationException("Test app ex", new Exception("Test inner ex"));
}
static void Main(string[] args)
{
try
{
try
{
try
{
CallAndThrow();
}
catch (Exception ex)
{
var dispatchException = ExceptionDispatchInfo.Capture(ex);
// rollback tran, etc
dispatchException.Throw();
}
}
catch (Exception ex)
{
var dispatchException = ExceptionDispatchInfo.Capture(ex);
// other rollbacks
dispatchException.Throw();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.InnerException.Message);
Console.WriteLine(ex.StackTrace);
}
Console.ReadLine();
}
Run Code Online (Sandbox Code Playgroud)
输出将类似于:
测试应用程序前 测试内外部 在 D:\Projects\TestApp\TestApp\Program.cs 中的 TestApp.Program.CallAndThrow() 处:第 19 行 在 D:\Projects\TestApp\TestApp\Program.cs 中的 TestApp.Program.Main(String[] args):第 30 行 --- 从先前抛出异常的位置开始的堆栈跟踪结束 --- 在 System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() 在 D:\Projects\TestApp\TestApp\Program.cs 中的 TestApp.Program.Main(String[] args):第 38 行 --- 从先前抛出异常的位置开始的堆栈跟踪结束 --- 在 System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() 在 D:\Projects\TestApp\TestApp\Program.cs 中的 TestApp.Program.Main(String[] args):第 47 行