Почему стек исключается в 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();
}