Неправильный номер строки трассировки стека внутри каждого блока foreach

#c# #for-loop #exception #.net-4.0 #stack-trace

#c# #for-цикл #исключение #.net-4.0 #трассировка стека

Вопрос:

У меня возникли проблемы с пониманием трассировки стека в .net 4. Я получаю разные номера строк для идентичного кода, выполняемого в консольном приложении или веб-службе, размещенной на IIS 7.5 (оба используют .net 4).

Консольное приложение:

 static void Main(string[] args)
{
    try
    {
        var test = new List<int>() { 1, 2, 3 };
        foreach (var root in test)
        {
            throw new Exception("foobar");
        }
    }
    catch(Exception e)
    {
        throw;
    }
}
  

При проверке номера строки трассировки стека «e» внутри блока catch я получаю «8», что я исключил (строка создания нового исключения («foobar»))

Веб-сервис

 [WebMethod]
public void Test()
{
    try
    {
        var test = new List<int>() { 1, 2, 3 };
        foreach (var root in test)
        {
            throw new Exception("foobar");
        }
    }
    catch (Exception e)
    {
        throw;
    }
}
  

Здесь все становится странным — при проверке номера строки трассировки стека я получаю «7», что является началом каждого блока foreach. То же самое происходит с классическим for , но, например, оператор if работает нормально.

Есть идеи?

Редактировать:

Что касается ответа машинного обучения о несоответствии между ide или строкой времени выполнения и pdb. Если я добавлю бессмысленные строки перед исключением и некоторые после, я все равно получу аналогичное поведение. Пример:

 [WebMethod]
public string Test()
{
    try
    {
        var test = new List<int>() { 1, 2, 3 };

        var a = 1;
        var b = 2;
        var c = 3;

        foreach (var root in test)
        {
            var d = 4;
            var e = 5;
            var f = 6;
            throw new Exception("foobar"   (new System.Diagnostics.StackFrame(0, true)).GetFileLineNumber());
            var g = 7;
        }
        return null;
    }
    catch (Exception e)
    {
        return e.Message   e.StackTrace;
    }
}
  

Здесь e.StackTrace сообщается о строке «12» ( foreach строка) и e.Message сообщается о строке «17», которая является правильной.

Ответ №1:

Это результат обработки исключений в Windows. Повторное удаление исключения, сгенерированного в том же методе, означает, что вы теряете информацию об исходном исключении, поскольку исключения относятся к области действия метода [1]. К сожалению, .NET унаследовал это ограничение, поскольку единственной альтернативой было реализовать исключения, не полагаясь на существующую инфраструктуру.

Если я правильно помню, это исправлено в 64-разрядном коде — убедитесь, что ваше консольное приложение работает как 64-разрядное, и оно должно работать должным образом. Скорее всего, это причина, по которой в вашем тестовом приложении все работает нормально, но не в вашем IIS (где код, скорее всего, выполняется как 32-разрядный и, возможно, с debug=false ). РЕДАКТИРОВАТЬ: на самом деле, похоже, это не так. В то время как разрядность задействована, это, скорее всего, связано с оптимизациями, выполняемыми дрожаниями — a foreach на 64-разрядной версии сообщает об исключении в foreach строке, в то время как замена foreach на a using GetEnumerator и т.д. Или изменение разрядности на 32-разрядную сообщит об исключении при повторном перемещении.

Если вы хотите избежать этой проблемы во всем коде, убедитесь, что вы никогда не перебрасываете исключения, которые изначально были сгенерированы тем же методом. Извлечение throw new ... в отдельный метод (и обеспечение того, чтобы он не был встроен, что происходит автоматически при наличии throw AFAIK в текущей среде выполнения MS) должно работать нормально. Не забывайте, что a foreach также содержит imlicit using , то есть finally предложение.

[1] Излишне говорить, что это немного упрощает. Базовая (встроенная) структурированная обработка исключений содержит только одно исключение на кадр стека — она не может отслеживать как исходное выбрасывание, так и повторное выбрасывание. Вы найдете много информации о том, как работает SEH, с помощью простого поиска в Google, если вам интересно углубиться 🙂

Комментарии:

1. Повторное перемещение не требуется, если вы проверите часть РЕДАКТИРОВАНИЯ. Его единственный блок try / catch и исключение в catch имеют номер строки блока for вместо строки внутри цикла. В IIS я работаю в 64-разрядной версии (для включения 32-разрядных приложений установлено значение false). Однако — я попытался запустить его непосредственно из VS, как предлагало машинное обучение, и он работает правильно! Похоже, это связано с IIS.

2. ОБНОВЛЕНИЕ — я установил для Enable 32-разрядных приложений значение true, и теперь номера строк правильные!

3. @PavleGartner Ну, это интересно. Ваш консольный код также прерывается в 64-битном? Убедитесь, что вы не подключили отладчик — это отключает многие оптимизации, которые могут вызвать проблему.

4. @PavleGartner При использовании 64-разрядного механизма исключения SEH кажется, что в таблице истории исключений есть только одна запись на кадр стека. Таким образом, кажется, что, хотя механизм сильно изменился, первоначальное ограничение все еще существует. В исходном механизме вы заменили номер строки на тот, который находится в rethrow. В 64-разрядной версии вместо этого вы получаете строку foreach ‘s. Интересно, что если я изменю foreach на руководство using с GetEnumerator помощью etc., он вернется к отображению строки rethrow . Так что в foreach может быть какой-то особый соус 🙂

5. @PavleGartner Это немного раздражает, да. Самый простой обходной путь — извлечь код в отдельные методы — имейте в виду, где находятся «границы исключений», и избегайте подобных случаев, когда исключение передается через несколько ограниченных разделов в одном и том же методе (случай foreach немного более коварный, но теперь, когда вы знаете …). На практике я не нашел это слишком сложным — у меня редко есть foreach, который содержит более нескольких строк. И сценарий, в котором вы перехватываете исключение, созданное тем же методом, уже пахнет забавно.