C# 投掷与再投掷:相同的结果?

C# 投掷与再投掷:相同的结果?,c#,exception-handling,throw,rethrow,C#,Exception Handling,Throw,Rethrow,参考网络上的大量文档,尤其是在这方面,例如: “扔e;”和“扔;”之间应该有区别 但是,从: 此代码: using System; class Ex { public static void Main() { // // First test rethrowing the caught exception variable. // Console.WriteLine("First test"); try { ThrowWithVariable();

参考网络上的大量文档,尤其是在这方面,例如: “扔e;”和“扔;”之间应该有区别

但是,从:

此代码:

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()
这与博客文章完全矛盾

通过以下代码可获得相同的结果:

原始问题:我做错了什么

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

总结:您的回答都很好,再次感谢

因此,原因是64位抖动导致的有效内联

我只能选择一个答案,以下是我选择LukeH答案的原因:

  • 他猜测了内联问题以及它可能与我的64位体系结构有关的事实

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

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

更新:此优化似乎符合以下要求:(谢谢0xA3)


提前感谢您的帮助。

使用调试版本,您将更清楚地看到差异。对于调试构建,第一次运行将显示位置为
throw ex
行,第二次运行将显示源自对
BadGuy
的实际调用。显然,“问题”在于打电话给坏蛋,而不是投球,你会用直接的
投球来追逐更少的鬼魂语句


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

我自己也尝试过运行这段代码,调试构建工作与我预期的一样,但我在发布构建中得到了与您相同的结果

我怀疑发生的是编译器内联只是用throw
newexception()替换了BadGuy()调用

如果在项目属性->构建屏幕中关闭“优化代码”选项,那么发布和调试构建都会产生相同的结果,在堆栈跟踪的顶部显示BadGuy()。

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

要查看这与抖动相关,可以使用属性装饰方法:

[MethodImpl(MethodImplOptions.NoOptimization)]
private static void ThrowWithoutVariable()
{
    try
    {
        BadGuy();
    }
    catch
    {
        throw;
    }
}
请注意,
throwwhithoutvariable
throwwhithvariable
的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页(我强调):

某些CIL指令执行隐式操作 运行时检查,确保内存和 类型安全最初,CLI 保证例外情况不会发生 精确,表示程序状态 在创建异常时保留 扔但是,强制执行精确 隐式检查的例外使 一些重要的优化 几乎不可能应用。 程序员现在可以通过 自定义属性,该方法是 “放松”,也就是说 由隐式运行时检查产生 不必精确

宽松的支票 保持可验证性(通过保持 内存和类型安全)而 允许重新排序的优化 说明书特别是 启用以下优化:

  • 执行隐式运行时检查 循环数
  • 重新排序循环迭代 (例如,矢量化和自动 (多线程)
  • 交换回路
  • 使内联的 方法的速度至少与 等效宏

我无法复制这个问题——使用.NET3.5(32位)可以得到与Bart文章中描述的相同的结果

我的猜测是.NET4编译器/jitter——或者如果这也发生在3.5版本下,那么可能是64位编译器/jitter——正在将
BadGuy
方法内联到调用方法中。尝试将以下属性添加到
BadGuy
中,看看这是否有任何区别:

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

另一方面,我发现有一次在博客上发布了一个hack(我已经丢失了引用),它允许您在rethrow时保留调用堆栈。如果您在一个上下文(例如,在运行异步操作的线程中)中捕获异常,并希望在另一个上下文(例如,在启动异步操作的另一个线程中)中重新触发异常,则此功能非常有用。它使用了一些未记录的功能,允许跨远程处理边界保留堆栈跟踪

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

不需要这样做,这就是InnerException的用途
抛出新异常(“我爆炸了”,ex)
。这真的很糟糕,不应该使用。@Paul,InnerException的问题是catch子句发生故障(它们需要捕获外部异常类型)。如果希望异步处理程序能够像调用同步版本一样解释异常,那么这将破坏透明度。有很多方法可以解决这个问题(你也可以让同步版本包装异常),但是在捕获方面会有点尴尬。@0xA3,我同意这很糟糕,但是,公平地说,MS通过让远程处理系统操纵异常状态作为其魔力的一部分,有效地做了同样的事情来支持远程处理