Почему в Exception.StackTrace стек усекается? - PullRequest
11 голосов
/ 12 ноября 2009

Почему верхняя часть стека (в Exception.StackTrace) усекается? Давайте посмотрим на простой пример:

public void ExternalMethod()
{
  InternalMethod();
}

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

Кажется, это "по замыслу". Но каковы причины такого странного дизайна? Это только усложняет отладку, потому что в сообщениях журнала я не могу понять, кто вызвал InternalMethod (), и часто эта информация очень необходима.

Что касается решений (для тех, кто не знает), как я понимаю, есть 2 общих решения:
1) Мы можем зарегистрировать статическое свойство Environment.StackTrace, которое содержит весь стек (например, начиная с самого высокого уровня (очереди сообщений) и заканчивая самым глубоким методом, в котором происходит исключение).
2) Мы должны ловить и регистрировать исключения на самых высоких уровнях. Когда нам нужно перехватить исключения на более низких уровнях, чтобы сделать что-то, нам нужно перебросить (с помощью оператора throw в C #) это далее.

Но вопрос о причинах такого замысла.

Ответы [ 4 ]

11 голосов
/ 12 ноября 2009

Хорошо, теперь я вижу, к чему вы клоните ... Извините за мое замешательство по поводу встраивания.

«Стек» в перехваченном исключении является только дельтой от текущего выполняемого блока перехвата к тому, где было сгенерировано исключение. Концептуально это поведение является правильным, поскольку 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();
        }
    }
2 голосов
/ 12 ноября 2009

Как сказал 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 имеет богатую инфраструктуру для обертывания исключений и повторного выброса, поэтому это не должно быть огромной проблемой.

0 голосов
/ 12 ноября 2009

Это часто вызывается оптимизацией компилятора.

Вы можете украсить методы, которые вы не хотите встроить, используя следующий атрибут:

[MethodImpl(MethodImplOptions.NoInlining)]
public void ExternalMethod()
{
  InternalMethod();
}
0 голосов
/ 12 ноября 2009

Я знаю, что в блоке перехвата, если вы делаете throw ex;, он усекает трассировку стека в этой точке. Вполне возможно, что это «специально» для броска, так как просто throw; не усекает стек в улове. То же самое может происходить и здесь, так как вы создаете новое исключение.

Что произойдет, если вы вызовете фактическое исключение (т. Е. int i = 100/0;)? Трассировка стека все еще усекается?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...