Что может привести к вызову reset callstack (я использую "throw", а не "throw ex" )

Я всегда считал, что разница между "throw" и "throw ex" заключалась в том, что в одиночку не было сброса stacktrace исключения.

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

using System;
using System.Text;

namespace testthrow2
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                try
                {
                    throw new Exception("line 14");
                }
                catch (Exception)
                {
                    throw; // line 18
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());

            }
            Console.ReadLine();
        }
    }
}

Я ожидаю, что этот код напечатает столбец, начинающийся с строки 14; однако столбец начинается с строки 18. Конечно, это не имеет большого значения в примере, но в моем реальном приложении, потеря исходной информации об ошибке довольно болезненна.

Я пропустил что-то очевидное? Есть ли другой способ достичь того, что я хочу (т.е. Повторить исключение, не теряя информацию о стеке?)

Я использую .net 3.5

Ответ 1

Вы должны прочитать эту статью:

Короче говоря, throw обычно сохраняет трассировку стека исходного исключенного исключения, но только если исключение не произошло в текущем стеке стека (т.е. метод).

Существует метод PreserveStackTrace (показан в этой статье в блоге), который используется для сохранения исходной трассировки стека следующим образом:

try
{

}
catch (Exception ex)
{
    PreserveStackTrace(ex);
    throw;
}

Но мое обычное решение состоит в том, чтобы просто не перехватывать и повторять исключения вроде этого (если это абсолютно необходимо) или просто всегда бросать новые исключения, используя свойство InnerException для распространения исходного исключения:

try
{

}
catch (Exception ex)
{
     throw new Exception("Error doing foo", ex);
}

Ответ 2

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

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

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

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

Ответ 3

Обычный метод rethrow сохраняет все в трассировке стека за исключением того, что если данный метод находится в трассировке стека, номер строки будет перезаписан. Это раздражает поведение. В С#, если нужно что-то сделать в исключительном случае, но все равно, что такое исключение, можно использовать шаблон:

  Boolean ok = False;
  try
  {
    do_something();
    ok = True;
  }
  finally
  {
    if (!ok) // An exception occurred!
      handle_exception();
  }

Есть номер, где этот шаблон очень полезен; наиболее распространенной будет функция, которая должна возвращать новый IDisposable. Если функция не вернется, одноразовый объект должен быть очищен. Обратите внимание, что любые операторы "return" в вышеуказанном блоке "try" должны установить ok в true.

В vb.net можно использовать шаблон, который функционально немного приятнее, хотя одно пятно в коде немного нехорошее, с шаблоном:

  Dim PendingException As Exception = Nothing;
  Try
    Do_Something
    PendingException = Nothing ' See note
  Catch Ex As Exception When CopyFirstParameterToSecondAndReturnFalse(Ex, PendingException )
    Throw ' Will never execute, since above will return false
  Finally
    If PendingException IsNot Nothing Then
      .. Handle exception
    EndIf
  End Try

Долгосрочная функция должна быть реализована очевидным образом. Преимущество этого шаблона состоит в том, чтобы сделать исключение доступным для кода. Хотя это часто не требуется в ситуациях с ручкой, но не в сети, есть одна ситуация, когда это может быть неоценимым: если процедура очистки генерирует исключение. Обычно, если процедура очистки генерирует исключение, любое ожидающее исключение будет потеряно. Однако с помощью вышеприведенного шаблона можно обернуть ожидающее исключение в исключение очистки.

Одна интересная заметка с приведенным выше кодом: возможно, что исключение достигло "Catch When", но все же для инструкции Try выполнить нормально. На самом деле неясно, что должно произойти в этом обстоятельстве, но ясно одно, что оператор "Окончательный" не должен действовать так, как будто ожидаемое исключение. Очистка PendingException сделает так, что если исключение исчезнет, ​​код будет вести себя так, как будто этого не произошло. Альтернативой было бы обернуть и перебросить исключение, которое, как известно, произошло, поскольку эта ситуация почти наверняка указывает на что-то неправильное с внутренним кодом обработки исключений.