代码之家  ›  专栏  ›  技术社区  ›  Pragmateek

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

  •  10
  • Pragmateek  · 技术社区  · 14 年前

    What is the proper way to re-throw an exception in C#? “throw e;”和“throw;”之间应该有区别。

    但是,从: 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

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

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

    卢克 回答:

    • 他猜测内联问题可能与我的64位体系结构有关,

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

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

    更新 Throw VS rethrow : same result? (谢谢 0xA3型 )

    5 回复  |  直到 8 年前
        1
  •  4
  •   LukeH    14 年前

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

    我的猜测是.NET4编译器/jitter——或者可能是64位编译器/jitter,如果这也发生在3.5版本下——正在内联 BadGuy 方法转换为调用方法。尝试添加以下内容 MethodImpl 属性为 坏痞子 看看这有什么不同:

    [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
    private static void BadGuy()
    {
        //
        // Some nasty behavior.
        //
        throw new Exception();
    }
    
        2
  •  3
  •   theburningmonk    14 年前

    我自己也试过运行这段代码,调试版本正如我预期的那样工作,但是我在发布版本中得到了与您相同的结果。

    new Exception(); 因为这是BadGuy()中唯一的语句。

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

        3
  •  3
  •   Community CDub    4 年前

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

    要了解这与抖动有关,可以使用 MethodImplAttribute 属性:

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

    请注意,IL对于 ThrowWithoutVariable ThrowWithVariable :

    .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编译器启用重要的优化。 Annex F 第52页陈述(我强调):

    某些CIL指令执行隐式 类型安全。 最初,CLI 保证例外情况 ,表示程序状态 在发生异常时保留 扔了。 然而,执行精确的 隐式检查的例外使 几乎不可能应用。 自定义属性,方法是 放松,也就是说 不必精确。

    保持可验证性(通过保持 内存和类型安全) 允许重新排序的优化 说明。尤其是 启用以下优化:

    • 运行时签出
    • 重新排序循环迭代 多线程)
    • 交换回路
    • 使内联 方法的速度至少和
        4
  •  1
  •   Paul Alexander    14 年前

    使用调试构建,您将更清楚地看到差异。对于调试构建,第一次运行会将位置显示为 throw ex 第二行和第二行是从对的实际调用开始的 BadGuy throw; 声明。

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

        5
  •  0
  •   Dan Bryant    14 年前

    另一方面,我在一个博客上发现了一个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;
        }