投掷vs rethrow:相同的结果吗?
-
30-09-2019 - |
题
指网上的大量文档,特别是在SO上,例如: 在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的结果相同
总结 :您的所有答案都很好,再次感谢。
因此,由于64位抖动,原因是有效地融合的。
我只需选择一个答案,这就是为什么我选择的原因 卢克 回答 :
他猜到了内部问题,事实可能与我的64位架构有关,
他提供了Noinlining Flag,这是避免这种行为的最简单方法。
但是,这个问题现在提出了另一个问题: 这种行为是否符合所有.NET规范:CLR的规范和C#编程语言?
更新 :根据: 投掷vs rethrow:相同的结果吗? (谢谢 0xa3)
在此先感谢您的帮助。
解决方案
我无法复制问题 - 使用.NET 3.5(32位)给我与Bart文章中描述的相同结果。
我的猜测是.NET 4编译器/抖动 - 或者,如果这也是3.5下的64位编译器/抖动 - BadGuy
调用方法中的方法。尝试添加以下内容 MethodImpl
属性为 BadGuy
看看这是否有任何区别:
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
private static void BadGuy()
{
//
// Some nasty behavior.
//
throw new Exception();
}
其他提示
我本人尝试过运行此代码,并且按照我的预期运行调试构建工作,但是我在发布版本中获得了与您相同的结果。
我怀疑发生了什么事是编译器内线简单地替换了BadGuy()电话 new Exception();
因为这是badguy()中的唯一陈述。
如果您在项目属性 - >“构建”屏幕中关闭“优化代码”选项,则释放和调试构建都会产生相同的结果,该结果显示在堆栈跟踪的顶部显示BadGuy()。
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编译器实现重要的优化。 附件f 第52页的国家(我强调):
一些CIL说明执行隐式运行时间检查,以确保内存和键入安全性。 最初,CLI保证例外是精确的, ,这意味着在抛出例外时保留了该程序状态。 但是,对隐式检查执行精确的例外,几乎无法应用一些重要的优化。程序员现在可以通过自定义属性声明一种方法是“放松的”,该方法说,由隐式运行时间检查引起的例外不必精确。
放松的检查可以保留可验证性(通过保留内存和键入安全性),同时允许重新排序指令进行优化。特别是,它可以实现以下优化:
- 吊装隐式运行时检查循环。
- 重新排序循环迭代(例如,矢量化和自动多线程)
- 互换循环
- 嵌入方式使您的内线方法至少与等效宏一样快
使用调试构建,您会更清楚地看到差异。通过调试构建,第一次运行将显示该位置为 throw ex
线,第二个是由实际调用到的 BadGuy
. 。显然,“问题”是打电话给Badguy-而不是掷球,您将更少的鬼魂与直接搜寻 throw;
陈述。
在堆栈痕迹中,这种浅的好处并不那么明显,在一个非常深的堆栈中,您会掩盖问题的实际来源,并通过手动抛出异常而不是使用内置的重新发表语句来放松一些保真度。
附带说明,我发现一次在博客上发布的黑客(从那以后我丢失了参考文献),这使您可以保留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;
}