Почему стек исключается в Exception.StackTrace?
Почему высокая часть стека (в 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) Мы должны улавливать и регистрировать исключения на самых высоких уровнях. Когда нам нужно перехватывать исключения на более низких уровнях, чтобы что-то сделать, нам нужно перебросить (с выражением "throw" в С#), чтобы он продвигался дальше.
Но вопрос о причинах такого дизайна.
Ответы
Ответ 1
Хорошо, теперь я вижу, что вы получаете... Извините за мое замешательство в том, что я делаю.
"Стек" в исключенном исключении - это только дельта из текущего исполняемого блока catch, где было выбрано исключение. Концептуально это поведение правильное в том, что Exception.StackTrack сообщает вам, где исключение произошло в контексте этого блока try/catch. Это позволяет пересылать стеки исключений через "виртуальные" вызовы и поддерживать точность. Одним из классических примеров этого является .Net Исключение исключений.
Таким образом, если вы хотите получить полный отчет стека в блоке 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();
}
}
Ответ 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 имеет богатую инфраструктуру для обертывания исключений и реорганизации, поэтому это не должно быть огромной сделкой.
Ответ 3
Я знаю, что в блоке catch, если вы выполняете throw ex;
, он обрезает трассировку стека в этой точке. Возможно, что он "по дизайну" для броска, так как только throw;
не обрезает стек в catch. То же самое может происходить здесь, так как вы выбрасываете новое исключение.
Что произойдет, если вы вызываете фактическое исключение (т.е. int i = 100/0;
)? Является ли трассировка стека еще усеченной?
Ответ 4
Это часто связано с оптимизацией компилятора.
Вы можете украсить методы, которые вы не хотите встроить, используя следующий атрибут:
[MethodImpl(MethodImplOptions.NoInlining)]
public void ExternalMethod()
{
InternalMethod();
}