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

时间:2010-08-23 22:15:17

标签: c# exception-handling throw rethrow

参考网上的大量文档,特别是SO,例如:What is the proper way to re-throw an exception in C#? “扔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#编程语言?

更新:此优化似乎符合:Throw VS rethrow : same result?(感谢 0xA3

提前感谢您的帮助。

5 个答案:

答案 0 :(得分:4)

我无法复制问题 - 使用.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();
}

答案 1 :(得分:3)

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

我怀疑正在发生的事情是编译器内联只是用throw new Exception();替换了BadGuy()调用,因为这是BadGuy()中唯一的语句。

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

答案 2 :(得分:3)

似乎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编译器启用重要的优化。第{52}页Annex F州(我强调):

  

某些CIL指令执行隐式   运行时检查确保内存和   类型安全。 最初是CLI   保证例外   精确,意思是程序状态   当一个例外时被保留了   抛出。 然而,执行准确   隐式检查的例外情况   一些重要的优化   几乎不可能申请。   程序员现在可以通过a声明   自定义属性,即方法   “放松”,这说明例外   来自隐式运行时检查   不必准确。

     

轻松检查   保持可验证性(通过保留   记忆和类型安全)而   允许重新排序的优化   说明。特别是它   启用以下优化:

     
      
  • 提升隐式运行时检查   循环。
  •   
  • 重新排序循环迭代   (例如,矢量化和自动化   多线程)
  •   
  • 交换循环
  •   
  • 内联使内联   方法至少和   等效宏
  •   

答案 3 :(得分:1)

使用调试版本,您将更清楚地看到差异。使用调试版本时,第一次运行会将位置显示为throw ex行,第二次运行将显示为实际调用BadGuy。显然,'问题'是对BadGuy的调用 - 而不是抛出前线,你会用直接throw;语句追逐更少的幽灵。

在堆栈跟踪中,这个浅层的好处并不像immediatley那样明显,在一个非常深的堆栈中,你将掩盖问题的实际来源并通过手动抛出异常而不是使用内置的重新抛出来保留一些保真度言。

答案 4 :(得分:0)

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

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