что может привести к сбросу сброса callstack (я использую "throw", а не "throw ex") - PullRequest
25 голосов
/ 01 марта 2011

Я всегда думал, что разница между «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 ]

34 голосов
/ 01 марта 2011

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

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

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

try
{

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

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

try
{

}
catch (Exception ex)
{
     throw new Exception("Error doing foo", ex);
}
9 голосов
/ 01 марта 2011

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

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

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

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

2 голосов
/ 01 марта 2011

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

  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 сделает так, что если исключение исчезнет, ​​код будет вести себя так, как будто этого никогда не было. В качестве альтернативы можно заключить в оболочку и выбросить исключение, о котором известно, что оно произошло, поскольку эта ситуация почти наверняка указывает на что-то не так с внутренним кодом обработки исключений.

...