为什么在Exception.StackTrace中会截断堆栈?

时间:2009-11-11 22:17:40

标签: c# .net exception architecture frameworks

为什么堆栈的高位(在Exception.StackTrace中)会被截断? 让我们看一个简单的例子:

public void ExternalMethod()
{
  InternalMethod();
}

public void InternalMethod()
{
  try
  {
    throw new Exception();
  }
  catch(Exception ex)
  {
    // ex.StackTrace here doesn't contain ExternalMethod()!
  }
}

这似乎是“按设计”。但这种奇怪设计的原因是什么?它只会使调试变得更复杂,因为在日志消息中我无法理解谁调用了InternalMethod(),而且这些信息通常是非常必要的。

至于解决方案(对于那些不知道的人),我理解有两种通用解决方案:
1)我们可以记录静态Environment.StackTrace属性,它包含整个堆栈(例如,从hiest级别(消息队列)开始并以发生异常的最深层方法结束)。
2)我们必须捕获并记录最高级别的异常。当我们需要捕获较低级别的异常来做某事时,我们需要重新抛出(在C#中使用“throw”语句)它会进一步上升。

但问题是这种设计的原因。

4 个答案:

答案 0 :(得分:11)

好的,现在我看到你得到了什么...抱歉我对内联的事情感到困惑。

捕获的异常中的“堆栈”只是从当前正在执行的catch块到抛出异常的位置的增量。从概念上讲,这种行为是正确的,因为Exception.StackTrack会告诉您在此try / catch块的上下文中发生异常的位置。这允许异常堆栈在“虚拟”调用之间转发,并仍然保持准确性。这样做的一个典型例子是.Net Remoting异常。

因此,如果您想在catch块中获得完整的堆栈报告,则可以将当前堆栈添加到异常堆栈中,如下例所示。唯一的问题是这可能会更贵。

    private void InternalMethod()
    {
        try
        {
            ThrowSomething();
        }
        catch (Exception ex)
        {
            StackTrace currentStack = new StackTrace(1, true);
            StackTrace exceptionStack = new StackTrace(ex, true);
            string fullStackMessage = exceptionStack.ToString() + currentStack.ToString();
        }
    }

答案 1 :(得分:2)

正如csharptest所说这是设计的。 StackTrace在try块停止。更进一步说,在抛出异常时调用框架中没有钩子。

所以你能做的最好的事情就是这些,它是获得完整堆栈跟踪的绝对要求(在创建异常时存储完整的跟踪):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.CompilerServices;
using System.Diagnostics;

namespace ConsoleApplication15 {

    [global::System.Serializable]
    public class SuperException : Exception {

        private void SaveStack() {
            fullTrace = Environment.StackTrace;
        }

        public SuperException() { SaveStack(); }
        public SuperException(string message) : base(message) { SaveStack();  }
        public SuperException(string message, Exception inner) : base(message, inner) { SaveStack(); }
        protected SuperException(
          System.Runtime.Serialization.SerializationInfo info,
          System.Runtime.Serialization.StreamingContext context)
            : base(info, context) { }

        private string fullTrace; 
        public override string StackTrace {
            get {
                return fullTrace;
            }
        }
    }

    class Program {

        public void ExternalMethod() {
            InternalMethod();
        }

        public void InternalMethod() {
            try {
                ThrowIt();
            } catch (Exception ex) {
                Console.WriteLine(ex.StackTrace);
            }
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void ThrowIt() {
            throw new SuperException();
        }


        static void Main(string[] args) {
            new Program().ExternalMethod();
            Console.ReadKey();
        }
    }
}

输出:

 
     at System.Environment.get_StackTrace()
   at ConsoleApplication15.SuperException..ctor() in C:\Users\sam\Desktop\Source
\ConsoleApplication15\ConsoleApplication15\Program.cs:line 17
   at ConsoleApplication15.Program.ThrowIt() in C:\Users\sam\Desktop\Source\Cons
oleApplication15\ConsoleApplication15\Program.cs:line 49
   at ConsoleApplication15.Program.InternalMethod() in C:\Users\sam\Desktop\Sour
ce\ConsoleApplication15\ConsoleApplication15\Program.cs:line 41
   at ConsoleApplication15.Program.Main(String[] args) in C:\Users\sam\Desktop\S
ource\ConsoleApplication15\ConsoleApplication15\Program.cs:line 55
   at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
   at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, C
ontextCallback callback, Object state)
   at System.Threading.ThreadHelper.ThreadStart()

不可能将这种行为注入到现有的系统定义的异常中,但.Net有一个丰富的基础设施来包装异常和重新抛出,所以它不应该是一个大问题。

答案 2 :(得分:0)

我知道如果你执行throw ex;,它会在catch块中截断堆栈跟踪。它可能是“按设计”抛出,因为只有throw;不会截断catch中的堆栈。同样可能会在这里发生,因为你要抛出一个新的异常。

如果您导致实际异常(即int i = 100/0;)会怎样?堆栈跟踪是否仍然被截断?

答案 3 :(得分:-1)

这通常是由编译器优化引起的。

您可以使用以下属性修饰您不想内联的方法:

[MethodImpl(MethodImplOptions.NoInlining)]
public void ExternalMethod()
{
  InternalMethod();
}