抛出VS重新抛出:同样的结果?

在网上引用了很多文档,特别是在SO上,例如: 在C#中重新抛出exception的正确方法是什么? “扔e”之间应该有区别 和“扔;”。

但是,来自: http : //bartdesmet.net/blogs/bart/archive/2006/03/12/3815.aspx ,

这段代码:

using System; class Ex { public static void Main() { // // First test rethrowing the caught exception variable. // Console.WriteLine("First test"); try { ThrowWithVariable(); } catch (Exception ex) { Console.WriteLine(ex.StackTrace); } // // Second test performing a blind rethrow. // Console.WriteLine("Second test"); try { ThrowWithoutVariable(); } catch (Exception ex) { Console.WriteLine(ex.StackTrace); } } private static void BadGuy() { // // Some nasty behavior. // throw new Exception(); } private static void ThrowWithVariable() { try { BadGuy(); } catch (Exception ex) { throw ex; } } private static void ThrowWithoutVariable() { try { BadGuy(); } catch { throw; } } } 

给出以下结果:

 $ /cygdrive/c/Windows/Microsoft.NET/Framework/v4.0.30319/csc.exe Test.cs Microsoft (R) Visual C# 2010 Compiler version 4.0.30319.1 Copyright (C) Microsoft Corporation. All rights reserved. $ ./Test.exe First test at Ex.ThrowWithVariable() at Ex.Main() Second test at Ex.ThrowWithoutVariable() at Ex.Main() 

这与博客文章完全矛盾。

使用以下代码获得相同类型的结果: http : //crazorsharp.blogspot.com/2009/08/rethrowing-exception-without-resetting.html

原始问题 :我做错了什么?

更新 :与.Net 3.5 / csc.exe 3.5.30729.4926相同的结果

SUMUP :你的所有答案都很棒,再次感谢。

因此,由于64位JITter,原因在于有效内联。

我不得不只选择一个答案,这就是我选择LukeH答案的原因:

  • 他猜到了内联问题以及它可能与我的64位架构相关的事实,

  • 他提供了NoInlining标志,这是避免这种行为的最简单方法。

然而,这个问题现在提出了另一个问题: 这种行为是否符合所有.Net规范:CLR和C#编程语言?

更新 :此优化似乎符合: 投掷VS重新抛出:相同的结果? (感谢0xA3

在此先感谢您的帮助。

我无法复制这个问题 – 使用.NET 3.5(32位)给出了Bart的文章中描述的相同结果。

我的猜测是.NET 4编译器/抖动 – 或者也许是64位编译器/抖动,如果这也发生在3.5以下 – 将BadGuy方法内联到调用方法中。 尝试将以下MethodImpl属性添加到BadGuy ,看看是否有任何区别:

 [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] private static void BadGuy() { // // Some nasty behavior. // throw new Exception(); } 

我自己尝试运行此代码,调试版本按预期工作,但我在发布版本中得到了与您相同的结果。

我怀疑正在发生的事情是编译器内联只是用throw new Exception();取代了BadGuy()调用new Exception(); 因为这是BadGuy()中唯一的声明。

如果在项目属性 – >构建屏幕中关闭“优化代码”选项,则Release和Debug构建产生相同的结果,在堆栈跟踪的顶部显示BadGuy()。

似乎JIT优化器在这里做了一些工作。 如您所见,第二种情况下的调用堆栈与运行Debug构建时的第一种情况不同。 但是,在Release版本中,由于优化,两个调用堆栈都是相同的。

要查看这与抖动有关,可以使用MethodImplAttribute属性修饰方法:

 [MethodImpl(MethodImplOptions.NoOptimization)] private static void ThrowWithoutVariable() { try { BadGuy(); } catch { throw; } } 

请注意,对于ThrowWithoutVariableThrowWithVariable ,IL仍然不同:

 .method private hidebysig static void ThrowWithVariable() cil managed { // Code size 11 (0xb) .maxstack 1 .locals init ([0] class [mscorlib]System.Exception ex) .try { IL_0000: call void Ex::BadGuy() IL_0005: leave.s IL_000a } // end .try catch [mscorlib]System.Exception { IL_0007: stloc.0 IL_0008: ldloc.0 IL_0009: throw } // end handler IL_000a: ret } // end of method Ex::ThrowWithVariable .method private hidebysig static void ThrowWithoutVariable() cil managed { // Code size 11 (0xb) .maxstack 1 .try { IL_0000: call void Ex::BadGuy() IL_0005: leave.s IL_000a } // end .try catch [mscorlib]System.Object { IL_0007: pop IL_0008: rethrow } // end handler IL_000a: ret } // end of method Ex::ThrowWithoutVariable 

更新以回答您的后续问题是否符合CLI规范

实际上它是兼容的,即允许JIT编译器启用重要的优化。 附件F第52页说明(我强调):

某些CIL指令执行隐式运行时检查,以确保内存和类型安全。 最初,CLI保证exception是精确的 ,这意味着抛出exception时保留了程序状态。 但是,对隐式检查强制执行精确的exception会使一些重要的优化实际上无法应用。 程序员现在可以通过自定义属性声明方法是“放松的”,这表示隐式运行时检查产生的exception不需要精确。

轻松检查可保持可validation性(通过保留内存和类型安全性),同时允许重新排序指令的优化。 特别是,它支持以下优化:

  • 从循环中提取隐式运行时检查。
  • 重新排序循环迭代(例如,矢量化和自动multithreading)
  • 交换循环
  • 内联使得内联方法的速度与等效宏一样快

使用调试版本,您将更清楚地看到差异。 使用调试版本,第一次运行将显示该位置作为throw ex line,第二次运行将显示为对BadGuy的实际调用。 显然,’问题’是对BadGuy的调用 – 而不是投掷前线,你会用直接throw;追逐更少的鬼魂throw; 声明。

在堆栈跟踪中,这种浅薄的好处并不像immediatley那样明显,在非常深的堆栈中,您将通过手动抛出exception而不是使用内置的重新抛出语句来掩盖问题的实际来源并失去一些保真度。

在旁注中,我发现在博客上发布了一次黑客攻击(我已经丢失了引用),允许您在重新抛出时保留调用堆栈。 如果您在一个上下文中捕获exception(例如,在运行异步操作的线程中)并希望在另一个上下文中重新抛出exception(例如,在启动异步操作的另一个线程中),这将非常有用。 它利用包含的一些未记录的function来允许跨越远程边界保留堆栈跟踪。

  //This terrible hack makes sure track trace is preserved if exception is re-thrown internal static Exception AppendStackTrace(Exception ex) { //Fool CLR into appending stack trace information when the exception is re-thrown var remoteStackTraceString = typeof(Exception).GetField("_remoteStackTraceString", BindingFlags.Instance | BindingFlags.NonPublic); if (remoteStackTraceString != null) remoteStackTraceString.SetValue(ex, ex.StackTrace + Environment.NewLine); return ex; }