为什么堆栈的高位(在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”语句)它会进一步提升。
但问题是关于这种设计的原因。
好的,现在我看到你得到了什么...抱歉我对内联的东西感到困惑。
捕获异常中的“堆栈”仅是从当前正在执行的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();
}
}
正如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();
}
}
}
输出:
在System.Environment.get_StackTrace()
在C:\ Users \ sam \ Desktop \ Source中的ConsoleApplication15.SuperException..ctor()
\ ConsoleApplication15 \ ConsoleApplication15 \ Program.cs:第17行
在C:\ Users \ sam \ Desktop \ Source \ Cons中的ConsoleApplication15.Program.ThrowIt()
oleApplication15 \ ConsoleApplication15 \ Program.cs:第49行
在C:\ Users \ sam \ Desktop \ Sour中的ConsoleApplication15.Program.InternalMethod()中
ce \ ConsoleApplication15 \ ConsoleApplication15 \ Program.cs:第41行
在C:\ Users \ sam \ Desktop \ S中的ConsoleApplication15.Program.Main(String [] args)
ource \ ConsoleApplication15 \ ConsoleApplication15 \ Program.cs:第55行
在System.AppDomain._nExecuteAssembly(程序集程序集,String [] args)
在Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
在System.Threading.ExecutionContext.Run(ExecutionContext executionContext,C
ontextCallback回调,对象状态)
在System.Threading.ThreadHelper.ThreadStart()
不可能将此行为注入到现有的系统定义的异常中,但.Net具有丰富的基础结构,用于包装异常和重新抛出,因此它不应该是一个大问题。
如果你这样做,我知道在一个陷阱中 throw ex;
它会截断该点的堆栈跟踪。只是因为投掷它可能是“按设计” throw;
不会截断catch中的堆栈。同样可能会在这里发生,因为你抛出了一个新的异常。
如果您造成实际例外会发生什么(即 int i = 100/0;
)?堆栈跟踪是否仍然被截断?
这通常是由编译器优化引起的。
您可以使用以下属性装饰您不想内联的方法:
[MethodImpl(MethodImplOptions.NoInlining)]
public void ExternalMethod()
{
InternalMethod();
}