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

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

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

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

3 ответа

Решение

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

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

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

try
{

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

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

try
{

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

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

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

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

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

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

  Boolean ok = False;
  пытаться
  {
    сделай что-нибудь();
    хорошо = правда;
  }
  в конце концов
  {
    if (!ok) // Возникла исключительная ситуация!
      handle_exception();
  }

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

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

  Dim PendingException As Exception = Nothing;
  Пытаться
    Сделай что-нибудь
    PendingException = Nothing 'См. Примечание
  Поймать Ex как исключение, когда CopyFirstParameterToSecondAndReturnFalse(Ex, PendingException)
    Throw 'никогда не выполнится, так как выше вернет false
  в заключение
    Если PendingException ничего не значит.. Обрабатывать исключение
    EndIf
  Конец попробовать

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

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

Другие вопросы по тегам