C# 通过重新刷新进行的堆栈跟踪不正确
我使用“throw;”重新引发异常,但stacktrace不正确:C# 通过重新刷新进行的堆栈跟踪不正确,c#,stack-trace,throw,rethrow,C#,Stack Trace,Throw,Rethrow,我使用“throw;”重新引发异常,但stacktrace不正确: static void Main(string[] args) { try { try { throw new Exception("Test"); //Line 12 } catch (Exception ex) { throw; //Line 15 } } catch (Exception
static void Main(string[] args) {
try {
try {
throw new Exception("Test"); //Line 12
}
catch (Exception ex) {
throw; //Line 15
}
}
catch (Exception ex) {
System.Diagnostics.Debug.Write(ex.ToString());
}
Console.ReadKey();
}
正确的堆栈跟踪应该是:
但我得到:
但第15行是“投掷”的位置。我已经用.NET 3.5对此进行了测试。这是因为您从第12行捕获了
异常
,并在第15行重新捕获它,因此堆栈跟踪将其作为现金,从那里抛出异常
为了更好地处理异常,您应该简单地使用
try…finally
,让未处理的异常出现。在同一个方法中抛出两次可能是一种特殊情况-我无法创建堆栈跟踪,其中同一方法中的不同行相互跟随。正如这个词所说,“堆栈跟踪”向您显示异常遍历的堆栈帧。每个方法调用只有一个堆栈帧
如果从另一个方法抛出,throw代码>不会删除Foo()
的条目,如预期:
static void Main(string[] args)
{
try
{
Rethrower();
}
catch (Exception ex)
{
Console.Write(ex.ToString());
}
Console.ReadKey();
}
static void Rethrower()
{
try
{
Foo();
}
catch (Exception ex)
{
throw;
}
}
static void Foo()
{
throw new Exception("Test");
}
如果修改Rethrower()
并替换throw代码>由抛出
,堆栈跟踪中的Foo()
项消失。同样,这是预期的行为。不确定这是否是出于设计,但我认为一直都是这样
如果原始抛出新异常位于单独的方法中,则抛出的结果应具有原始方法名称和行号,然后是重新抛出异常的main中的行号
如果您使用throw ex,那么结果将只是main中的一行,其中异常是rethrow
换句话说,throw ex会丢失堆栈跟踪的all,而throw会保留堆栈跟踪的history(即较低级别方法的详细信息)。但是,如果您的异常是通过与rethrow相同的方法生成的,那么您可能会丢失一些信息
注意。如果您编写了一个非常简单的小型测试程序,框架有时可以优化事情,并将方法更改为内联代码,这意味着结果可能与“真实”程序不同 这是Windows版CLR中众所周知的限制。它使用Windows内置的异常处理支持(SEH)。问题是,它是基于堆栈帧的,并且一个方法只有一个堆栈帧。通过将内部try/catch块移动到另一个helper方法中,从而创建另一个堆栈帧,可以轻松解决此问题。此限制的另一个后果是JIT编译器不会内联任何包含try语句的方法。Edit/Replace
这种行为实际上是不同的,但微妙地如此。至于为什么行为不同,我需要听从CLR专家的意见
编辑:似乎表明这是出于设计
在捕获异常的同一个方法中抛出异常会稍微混淆情况,因此让我们从另一个方法中抛出异常:
class Program
{
static void Main(string[] args)
{
try
{
Throw();
}
catch (Exception ex)
{
throw ex;
}
}
public static void Throw()
{
int a = 0;
int b = 10 / a;
}
}
如果抛出使用代码>时,调用堆栈为(行号替换为代码):
如果抛出ex使用代码>时,调用堆栈为:
at Main():line (throw ex;)
at Throw():line (int b = 10 / a;)
at Main():line (Throw())
如果未捕获异常,则调用堆栈为:
at Main():line (throw ex;)
at Throw():line (int b = 10 / a;)
at Main():line (Throw())
在.NET 4/VS 2010中进行了测试,这是可以被认为是意料之中的事情。
如果指定throw-ex,则修改堆栈跟踪是常见的情况代码>,FxCop将通知您堆栈已修改。如果你做抛出代码>,未生成警告,但仍将修改跟踪。
所以不幸的是,现在最好不要抓住前男友,或者把他当作一个内奸扔掉。
我认为它应该被视为是一个Windows影响或类似的smth-编辑。
杰夫·里克特(Jeff Richter)在他的“CLR通过C#”中更详细地描述了这种情况。:
下面的代码抛出相同的错误
它捕获的异常对象,并且
使CLR重置其启动
例外点:
private void SomeMethod() {
try { ... }
catch (Exception e) {
...
throw e; // CLR thinks this is where exception originated.
// FxCop reports this as an error
}
}
private void SomeMethod() {
try { ... }
catch (Exception e) {
...
throw; // This has no effect on where the CLR thinks the exception
// originated. FxCop does NOT report this as an error
}
}
相反,如果你再次抛出
通过使用抛出
关键字本身,CLR不会
重置堆栈的起点。这个
下面的代码重新抛出相同的
它捕获的异常对象,
导致CLR不重置其
异常的起点:
private void SomeMethod() {
try { ... }
catch (Exception e) {
...
throw e; // CLR thinks this is where exception originated.
// FxCop reports this as an error
}
}
private void SomeMethod() {
try { ... }
catch (Exception e) {
...
throw; // This has no effect on where the CLR thinks the exception
// originated. FxCop does NOT report this as an error
}
}
事实上,两者之间的唯一区别
这两个代码片段是
CLR认为是原始位置
抛出异常的位置。
不幸的是,当你投掷或
重新显示异常,Windows不会
重置堆栈的起点。So
如果异常未处理,
报告的堆栈位置
Windows错误报告是
最后一次投掷的位置或
重新抛出,即使CLR知道
原始文件所在的堆栈位置
引发了异常。这是
不幸的是,这会导致调试失败
在中失败的应用程序
这个领域要困难得多。一些
开发人员已经发现这一点
令人无法忍受的是,他们选择了
实现代码的不同方式
确保堆栈跟踪准确无误
反映了
异常最初被抛出:
private void SomeMethod() {
Boolean trySucceeds = false;
try {
...
trySucceeds = true;
}
finally {
if (!trySucceeds) { /* catch code goes in here */ }
}
}
如何保存真正的stacktrace
抛出一个新异常,并将原始异常作为内部异常包含
但那太难看了。。。比较长的使您选择要抛出的rigth异常
你对丑陋的看法是错误的,但对另外两点的看法是正确的。经验法则是:除非你打算用它做些什么,比如包装它、修改它、吞下它或记录它,否则不要抓。如果你决定捕捉
,然后再次抛出
,请确保你正在用它做些什么,否则就让它冒出来
您可能还想简单地放置一个catch,以便可以在catch中设置断点,但Visual Studio调试器有足够的选项使这种做法变得不必要,请尝试使用第一次机会异常或条件断点。我认为这不是堆栈跟踪更改的情况
class Class1
{
public int a;
public static void testWithoutTC()
{
Class1 obj = null;
obj.a = 1;
}
public static void testWithTC1()
{
try
{
Class1 obj = null;
obj.a = 1;
}
catch
{
throw;
}
}
public static void testWithTC2()
{
try
{
Class1 obj = null;
obj.a = 1;
}
catch (Exception ex)
{
throw ex;
}
}
}
private static void RethrowExceptionButPreserveStackTrace(Exception exception)
{
System.Reflection.MethodInfo preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace",
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
preserveStackTrace.Invoke(exception, null);
throw exception;
}
ExceptionDispatchInfo.Capture(ex);
static void CallAndThrow()
{
throw new ApplicationException("Test app ex", new Exception("Test inner ex"));
}
static void Main(string[] args)
{
try
{
try
{
try
{
CallAndThrow();
}
catch (Exception ex)
{
var dispatchException = ExceptionDispatchInfo.Capture(ex);
// rollback tran, etc
dispatchException.Throw();
}
}
catch (Exception ex)
{
var dispatchException = ExceptionDispatchInfo.Capture(ex);
// other rollbacks
dispatchException.Throw();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.InnerException.Message);
Console.WriteLine(ex.StackTrace);
}
Console.ReadLine();
}
Test app ex
Test inner ex
at TestApp.Program.CallAndThrow() in D:\Projects\TestApp\TestApp\Program.cs:line 19
at TestApp.Program.Main(String[] args) in D:\Projects\TestApp\TestApp\Program.cs:line 30
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at TestApp.Program.Main(String[] args) in D:\Projects\TestApp\TestApp\Program.cs:line 38
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at TestApp.Program.Main(String[] args) in D:\Projects\TestApp\TestApp\Program.cs:line 47