Неверный номер строки на трассе стека

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

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 для


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

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

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

Ответ 1

Да, это ограничение в логике обработки исключений. Если метод содержит более одного оператора 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 имеет один стек кадров, независимо от количества блоков области внутри метода. Используя вспомогательный метод, вы автоматически получаете еще один стек кадров, который может отслеживать свое собственное исключение. Джиттер также автоматически подавляет оптимизацию inlining, когда метод содержит оператор throw, поэтому нет необходимости явно использовать атрибут [MethodImpl].

Ответ 2

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

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

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

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

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

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

   В С#, как я могу перекрыть InnerException без потери трассировки стека?

Ответ 3

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

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

Ответ 4

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

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

Это, по-видимому, по дизайну.

Ответ 5

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

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

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

введите описание изображения здесь

Ответ 6

В .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.