Неверный stacktrace by rethrow

Я редуцирую исключение с "throw;", но stacktrace неверно:

static void Main(string[] args) {
    try {
        try {
            throw new Exception("Test"); //Line 12
        }
        catch (Exception ex) {
            throw; //Line 15
        }
    }
    catch (Exception ex) {
        System.Diagnostics.Debug.Write(ex.ToString());
    }
    Console.ReadKey();
}

Правильный стек должен быть:

System.Exception: Test
   at ConsoleApplication1.Program.Main(String[] args) in Program.cs:Line 12

Но я получаю:

System.Exception: Test
   at ConsoleApplication1.Program.Main(String[] args) in Program.cs:Line 15

Но строка 15 - это позиция "броска". Я тестировал это с помощью .NET 3.5.

Ответ 1

Бросок дважды в один и тот же метод, вероятно, является особым случаем - мне не удалось создать трассировку стека, где разные строки одного и того же метода следуют друг за другом. Как сказано в слове, "трассировка стека" показывает вам фреймы стека, которые пересекали исключение. И есть только один стек стека на вызов метода!

Если вы выбрали другой метод, throw; не удалит запись для Foo(), как ожидалось:

  static void Main(string[] args)
  {
     try
     {
        Rethrower();
     }
     catch (Exception ex)
     {
        Console.Write(ex.ToString());
     }
     Console.ReadKey();
  }

  static void Rethrower()
  {
     try
     {
        Foo();
     }
     catch (Exception ex)
     {
        throw;
     }

  }

  static void Foo()
  {
     throw new Exception("Test"); 
  }

Если вы измените Rethrower() и замените throw; на throw ex;, запись Foo() в трассировке стека исчезнет. Опять же, что ожидаемое поведение.

Ответ 2

Это то, что можно считать ожидаемым. Изменение трассировки стека является обычным делом, если вы укажете throw ex;, FxCop будет уведомлять вас о том, что этот стек изменен. В случае, если вы создаете throw;, никаких предупреждений не генерируется, но все же след будет изменен. Так что, к сожалению, на данный момент лучше не ловить экс или бросить его как внутренний. Я думаю, что это следует рассматривать как Эффект Windows или smth, подобный этому - отредактирован. Джефф Рихтер описывает эту ситуацию более подробно в своей "CLR через С#" :

Следующий код бросает то же самое объект исключения, который он поймал и вызывает CLR до reset его запуск точка исключения:

private void SomeMethod() {
  try { ... }
  catch (Exception e) {
    ...
    throw e; // CLR thinks this is where exception originated.
    // FxCop reports this as an error
  }
}

В отличие от этого, если вы повторно объект исключения, используя бросок ключевое слово самостоятельно, CLR doesnt reset начальная точка стека. следующий код повторяет то же самое объект исключения, который он поймал, в результате чего CLR не будет reset его отправной точкой для исключения:

private void SomeMethod() {
  try { ... }
  catch (Exception e) {
    ...
    throw; // This has no effect on where the CLR thinks the exception
    // originated. FxCop does NOT report this as an error
  }
}

Фактически, единственное различие между эти два фрагмента кода - это то, что CLR считает исходное местоположение где было исключено исключение. К сожалению, когда вы бросаете или Восстановить исключение, Windows делает reset стартовая точка стеков.если исключение становится необработанным, местоположение стека, которое сообщается to Windows Error Reporting - это местоположение последнего броска или re-throw, хотя CLR знает местоположение стека, где оригинал исключение было выбрано. Это неудачный, потому что он делает отладку приложения, которые потерпели неудачу в поле намного сложнее. Некоторые разработчики нашли это так невыносимо, что они выбрали другой способ реализовать свой код чтобы убедиться, что трассировка стека действительно отражает место, где Исключение было изначально выбрано:

private void SomeMethod() {
  Boolean trySucceeds = false;
  try {
    ...
    trySucceeds = true;
  }
  finally {
    if (!trySucceeds) { /* catch code goes in here */ }
  }
}

Ответ 3

Это хорошо известное ограничение в версии CLR для Windows. Он использует встроенную поддержку Windows для обработки исключений (SEH). Проблема в том, что он основан на стеке, и метод имеет только один стек. Вы можете легко решить проблему, перемещая внутренний блок try/catch в другой вспомогательный метод, создавая тем самым другой стек стека. Другим следствием этого ограничения является то, что компилятор JIT не будет внедрять какой-либо метод, содержащий инструкцию try.

Ответ 4

Как я могу сохранить REAL stacktrace?

Вы генерируете новое исключение и включаете исходное исключение в качестве внутреннего исключения.

но это Уродливое... Дольше... Делает выбор единственным исключением, чтобы бросить....

Вы ошибаетесь в отношении уродливых, но прав о двух других моментах. Эмпирическое правило: не поймайте, если вы не собираетесь что-то делать с ним, например, оберните его, измените, проглотите или запишите. Если вы решите catch и затем throw снова, убедитесь, что вы что-то делаете с ним, иначе просто дайте ему пузыриться.

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

Ответ 5

Редактировать/Заменить

Поведение на самом деле другое, но утонченное. Что касается того, почему поведение отличается от другого, мне нужно будет отнестись к эксперту по CLR.

EDIT: ответ AlexD, кажется, указывает, что это по дизайну.

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

class Program
{
    static void Main(string[] args)
    {
        try
        {
            Throw();
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }

    public static void Throw()
    {
        int a = 0;
        int b = 10 / a;
    }
}

Если используется throw;, столбец вызова (номера строк заменены кодом):

at Throw():line (int b = 10 / a;)
at Main():line (throw;) // This has been modified

Если используется throw ex;, столбец вызова:

at Main():line (throw ex;)

Если исключение не обнаружено, столбец вызова:

at Throw():line (int b = 10 / a;)
at Main():line (Throw())

Протестировано в .NET 4/VS 2010

Ответ 6

Существует дублированный вопрос здесь.

Как я понимаю - брось; скомпилирован в 'rethrow' MSIL инструкция, и он изменяет последний кадр трассировки стека.

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

Заключение: избегайте использования throw; и оберните ваше исключение в новое, когда вы повторно бросаете - это не уродливо, это лучшая практика.

Ответ 7

Вы можете сохранить трассировку стека, используя

ExceptionDispatchInfo.Capture(ex);

Вот пример кода:

    static void CallAndThrow()
    {
        throw new ApplicationException("Test app ex", new Exception("Test inner ex"));
    }

    static void Main(string[] args)
    {
        try
        {
            try
            {
                try
                {
                    CallAndThrow();
                }
                catch (Exception ex)
                {
                    var dispatchException = ExceptionDispatchInfo.Capture(ex);

                    // rollback tran, etc

                    dispatchException.Throw();
                }
            }
            catch (Exception ex)
            {
                var dispatchException = ExceptionDispatchInfo.Capture(ex);

                // other rollbacks

                dispatchException.Throw();
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            Console.WriteLine(ex.InnerException.Message);
            Console.WriteLine(ex.StackTrace);
        }

        Console.ReadLine();
    }

Выход будет выглядеть примерно так:

Test app ex
Test inner ex
   at TestApp.Program.CallAndThrow() in D:\Projects\TestApp\TestApp\Program.cs:line 19
   at TestApp.Program.Main(String[] args) in D:\Projects\TestApp\TestApp\Program.cs:line 30
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at TestApp.Program.Main(String[] args) in D:\Projects\TestApp\TestApp\Program.cs:line 38
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at TestApp.Program.Main(String[] args) in D:\Projects\TestApp\TestApp\Program.cs:line 47

Ответ 8

ОК, кажется, что в .NET Framework есть ошибка, если вы выбрали исключение и сбросили его в том же методе, исходный номер строки будет потерян (это будет последняя строка метода).

Fortunatelly, умный парень по имени Fabrice MARGUERIE нашел решение для этой ошибки. Ниже приведена моя версия, которую вы можете проверить в этом .NET скрипте.

private static void RethrowExceptionButPreserveStackTrace(Exception exception)
{
    System.Reflection.MethodInfo preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace",
      System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
    preserveStackTrace.Invoke(exception, null);
      throw exception;
}

Теперь поймайте исключение как обычно, но вместо throw; просто вызовите этот метод, и voila, номер исходной строки будет сохранен!

Ответ 9

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

Если исходный throw new Exception находится в отдельном методе, тогда результат для throw должен иметь исходное имя метода и номер строки, а затем номер строки в главном, где исключение повторно выбрано.

Если вы используете throw ex, тогда результат будет просто строкой в ​​главном, где исключение - rethrow.

Другими словами, throw ex теряет все стек, а throw сохраняет трассировку стека history (т.е. подробности методов нижнего уровня). Но если ваше исключение генерируется тем же методом, что и ваш ретрол, вы можете потерять некоторую информацию.

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

Ответ 10

Вам нужен номер вашей правой линии? Просто используйте один try/catch для каждого метода. В системах, ну... только в слое пользовательского интерфейса, а не в логике или доступе к данным, это очень раздражает, потому что, если вам нужны транзакции с базой данных, ну, они не должны находиться на уровне пользовательского интерфейса, и у вас не будет номер правой строки, но если они вам не нужны, не ревертируйте и не исключайте в catch...

Пример кода за 5 минут:

Файл меню → Новый проект, поместите три кнопки и вызовите следующий код в каждом из них:

private void button1_Click(object sender, EventArgs e)
{
    try
    {
        Class1.testWithoutTC();
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine + Environment.NewLine + "In. Ex.: " + ex.InnerException);
    }
}

private void button2_Click(object sender, EventArgs e)
{
    try
    {
        Class1.testWithTC1();
    }
    catch (Exception ex)
    {
            MessageBox.Show(ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine + Environment.NewLine + "In. Ex.: " + ex.InnerException);
    }
}

private void button3_Click(object sender, EventArgs e)
{
    try
    {
        Class1.testWithTC2();
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine + Environment.NewLine + "In. Ex.: " + ex.InnerException);
    }
}

Теперь создайте новый класс:

class Class1
{
    public int a;
    public static void testWithoutTC()
    {
        Class1 obj = null;
        obj.a = 1;
    }
    public static void testWithTC1()
    {
        try
        {
            Class1 obj = null;
            obj.a = 1;
        }
        catch
        {
            throw;
        }
    }
    public static void testWithTC2()
    {
        try
        {
            Class1 obj = null;
            obj.a = 1;
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }
}

Запустите... первая кнопка красива!

Ответ 11

Я думаю, что это меньше в случае изменения трассировки стека и больше зависит от того, как определяется номер строки для трассировки стека. Пробуя это в Visual Studio 2010, поведение похоже на то, что вы ожидаете от документации MSDN: "throw ex;" восстанавливает трассировку стека с точки этого утверждения, "throw"; оставляет трассировку стека как таковую, за исключением того, что когда бы ни было исключено исключение, номер строки - это местоположение ретрона, а не вызванное исключение.

Итак, с "throw"; дерево вызовов метода остается неизменным, но номера строк могут меняться.

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

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

Интересная заметка: Visual Studio 2010 даже не позволит мне создать код, представленный в вопросе, поскольку он собирает деление на нулевую ошибку во время компиляции.

Ответ 12

Это потому, что вы вытащили Exception из строки 12 и вернули его в строке 15, поэтому Stack Trace берет его как наличные деньги, из которого отбрасывается Exception.

Чтобы лучше обрабатывать исключения, вы должны просто использовать try...finally, и пусть необработанный Exception пузырь вверх.