Неверный номер строки в трассировке стека - PullRequest
30 голосов
/ 22 марта 2010

У меня есть этот код

try
{
  //AN EXCEPTION IS GENERATED HERE!!!
}
catch  
{
   SqlService.RollbackTransaction();
   throw;
}

Код выше называется в этом коде

try
{
  //HERE IS CALLED THE METHOD THAT CONTAINS THE CODE ABOVE
}
catch (Exception ex)
{
   HandleException(ex);
}

Исключение, переданное в качестве параметра методу «HandleException», содержит номер строки «throw» в трассировке стека вместо реальной строки, где было сгенерировано исключение. Кто-нибудь знает, почему это может происходить?

EDIT1 Хорошо, спасибо всем за ваши ответы. Я поменял внутреннюю защелку на


catch(Exception ex)
{
    SqlService.RollbackTransaction();
    throw new Exception("Enrollment error", ex);
}

Теперь у меня правильная строка в трассировке стека, но мне пришлось создать новое исключение. Я надеялся найти лучшее решение: - (

EDIT2 Возможно (если у вас есть 5 минут), вы можете попробовать этот сценарий, чтобы проверить, получаете ли вы тот же результат, не очень сложный для воссоздания.

Ответы [ 6 ]

36 голосов
/ 22 марта 2010

Да, это ограничение в логике обработки исключений. Если метод содержит более одного оператора throw, который выдает исключение, вы получите номер строки последнего, который выкинул. Этот пример кода воспроизводит это поведение:

using System;

class Program {
    static void Main(string[] args) {
        try {
            Test();
        }
        catch (Exception ex) {
            Console.WriteLine(ex.ToString());
        }
        Console.ReadLine();
    }
    static void Test() {
        try {
            throw new Exception();  // Line 15
        }
        catch {
            throw;                  // Line 18
        }
    }
}

Выход:

System.Exception: Exception of type 'System.Exception' was thrown.
   at Program.Test() in ConsoleApplication1\Program.cs:line 18
   at Program.Main(String[] args) in ConsoleApplication1\Program.cs:line 6

Обойти это просто, просто используйте вспомогательный метод для запуска кода, который может вызвать исключение.

Как это:

static void Test() {
    try {
        Test2();                // Line 15
    }
    catch {
        throw;                  // Line 18
    }
}
static void Test2() {
    throw new Exception();      // Line 22
}

Основная причина этого неуклюжего поведения заключается в том, что обработка исключений .NET построена поверх поддержки исключений операционной системой. Вызывается SEH, структурированная обработка исключений в Windows. Который основан на кадрах стека, в каждом кадре стека может быть только одно активное исключение. Метод .NET имеет один фрейм стека независимо от количества блоков области действия внутри метода. Используя вспомогательный метод, вы автоматически получаете другой фрейм стека, который может отслеживать свое собственное исключение. Джиттер также автоматически подавляет оптимизацию встраивания, когда метод содержит оператор throw , поэтому нет необходимости явно использовать атрибут [MethodImpl].

10 голосов
/ 09 ноября 2010

"Но throw; сохраняет трассировку стека !! Используйте throw; "

Сколько раз вы слышали об этом ... Ну, любой, кто какое-то время программировал на .NET, почти наверняка слышал это и, вероятно, принял это как все и завершил все "отбрасывающие" исключения.

К сожалению, это не всегда так. Как объясняет @hans, если код, вызывающий исключение, происходит в том же методе, что и оператор throw;, то трассировка стека сбрасывается в эту строку.

Одним из решений является извлечение кода из try, catch в отдельный метод, а другим решением является создание нового исключения с перехваченным исключением в качестве внутреннего исключения. Новый метод немного неуклюж, и new Exception() теряет исходный тип исключения, если вы пытаетесь перехватить его дальше в стеке вызовов.

Я нашел более подробное описание этой проблемы в Блог Фабрис Маргери .

НО еще лучше есть еще один вопрос StackOverflow, у которого есть решения (даже если некоторые из них требуют отражения):

Как в C # выкинуть InnerException без потери трассировки стека?

6 голосов
/ 28 марта 2017

Начиная с .NET Framework 4.5, вы можете использовать класс ExceptionDispatchInfo, чтобы сделать это без необходимости использования другого метода.Например, заимствуя код из превосходного ответа Ганса, когда вы просто используете throw, например:

using System;

class Program {
    static void Main(string[] args) {
        try {
            Test();
        }
        catch (Exception ex) {
            Console.WriteLine(ex.ToString());
        }
        Console.ReadLine();
    }
    static void Test() {
        try {
            throw new ArgumentException();  // Line 15
        }
        catch {
            throw;                          // Line 18
        }
    }
}

, выводится следующее:

System.ArgumentException: Value does not fall within the expected range.
   at Program.Test() in Program.cs:line 18
   at Program.Main(String[] args) in Program.cs:line 6

Но вы можете использоватьExceptionDispatchInfo для захвата и повторного выброса исключения, например:

using System;

class Program {
    static void Main(string[] args) {
        try {
            Test();
        }
        catch (Exception ex) {
            Console.WriteLine(ex.ToString());
        }
        Console.ReadLine();
    }
    static void Test() {
        try {
            throw new ArgumentException();              // Line 15
        }
        catch(Exception ex) {
            ExceptionDispatchInfo.Capture(ex).Throw();  // Line 18
        }
    }
}

Затем будет выведено следующее:

System.ArgumentException: Value does not fall within the expected range.
   at Program.Test() in Program.cs:line 15
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Program.Test() in Program.cs:line 18
   at Program.Main(String[] args) in Program.cs:line 6

Как видите, ExceptionDispatchInfo.Throw добавляет дополнительную информациюк трассировке стека исходного исключения, добавив тот факт, что оно было переброшено, но оно сохраняет исходный номер строки и тип исключения.Для получения дополнительной информации см. Документацию MSDN .

5 голосов
/ 22 марта 2010

Соответствует ли метка даты / времени вашего файла .pdb вашему файлу .exe / .dll? Если нет, возможно, компиляция не находится в «режиме отладки», который генерирует свежий файл .pdb для каждой сборки. Файл pdb содержит точные номера строк при возникновении исключений.

Просмотрите параметры компиляции, чтобы убедиться, что данные отладки сгенерированы, или, если вы находитесь в тестовой / производственной среде, проверьте файл .pdb, чтобы убедиться, что метки времени совпадают.

3 голосов
/ 22 марта 2010

Трассировки стека C # генерируются во время выброса, а не во время создания исключения.

Это отличается от Java, где трассировки стека заполняются во время создания исключения.

Это очевидно по замыслу.

1 голос
/ 09 сентября 2016

Я часто получаю это в производственных системах, если отмечен Optimize code. Это запутывает номера строк даже в 2016 году.

Убедитесь, что ваша конфигурация установлена ​​на «Release» или какую-либо конфигурацию, под которой вы строите и разворачиваете. Флажок имеет другое значение для конфигурации

В конечном итоге я никогда не знаю, насколько «оптимизирован» мой код с этой проверкой - поэтому проверьте его, если нужно, - но во многих случаях он сохранил мой след стека.

enter image description here

...