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

Я заметил странное поведение с номером строки в трассе стека исключений, если исключение выбрано внутри оператора switch.

Вот пример (вопросы форматирования, конечно, из-за номеров строк):

using System;
class Program {
    static void Main(string[] args) {
        for (int i = 0; i < 3; i++) {
            try {
                ThrowSomeException(i);
            } catch (Exception exc) {
                Console.WriteLine(exc);
            }
        }
    }
    private static void ThrowSomeException(int arg) {
        Console.WriteLine("arg = {0}", arg);
        switch (arg) {
        case 0:
            throw new Exception("Line number = 16");
        case 1:
            throw new Exception("Line number = 18");
        default:
            throw new Exception("Line number = 20");
        }
    }
}

Строка, указанная в трассировке стека, представляет собой строку исключения next в инструкции switch. Вышеупомянутая программа производит этот результат (обратите внимание на несоответствие номера строки в тексте исключения и номер строки в трассировке стека):

arg = 0
System.Exception: Line number = 16
   at Program.ThrowSomeException(Int32 arg) in x:\test\Program.cs:line 18
   at Program.Main(String[] args) in x:\test\Program.cs:line 6
arg = 1
System.Exception: Line number = 18
   at Program.ThrowSomeException(Int32 arg) in x:\test\Program.cs:line 20
   at Program.Main(String[] args) in x:\test\Program.cs:line 6
arg = 2
System.Exception: Line number = 20
   at Program.ThrowSomeException(Int32 arg) in x:\test\Program.cs:line 20
   at Program.Main(String[] args) in x:\test\Program.cs:line 6

Почему это происходит?

Примечание. Я пробовал это как с VS 2012, так и с 2013 года, компилируя оба из .NET 3.5 и 4.5 и получив те же результаты.

Debug vs Release: на удивление, я получаю это странное поведение только в Debug, в Release номера строк верны.

Ответы

Ответ 1

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

private static void ThrowSomeException(int arg)
{
    Console.WriteLine("arg = {0}", arg);
    switch (arg)
    {
        case 0:
        {
            throw new Exception("Line number = 16");
        }
        case 1:
        {
            throw new Exception("Line number = 18");
        }
    }
    throw new Exception("Line number = 20");
}

Это то, что Telerik JustDecompile сообщает мне, как выглядит метод, как в режиме отладки, так и в режиме Release. Очень возможно, что если вы просмотрите необработанную сборку, появится еще одно объяснение, почему это несоответствие существует.

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


Я провел еще несколько тестов. Я переместил функцию ThrowSomeException() в отдельный класс и сделал ее нестатической, и это ничего не изменило. Затем я немного переписал его, чтобы сначала назначить исключение переменной, а затем выбросить ее отдельно.

internal class Program
{
    private static void Main()
    {
        Test test = new Test();
        for (int i = 0; i < 3; i++)
        {
            try
            {
                test.ThrowSomeException(i);
            }
            catch (Exception exception)
            {
                Console.WriteLine(exception);
            }
        }
    }


}

public class Test
{
    public void ThrowSomeException(int arg)
    {

        Console.WriteLine("arg = {0}", arg);
        switch (arg)
        {
            case 0:
                {
                    Exception ex = new Exception("Line number = 36");
                    throw ex;
                }
            case 1:
                {
                    Exception ex = new Exception("Line number = 41");
                    throw ex;
                }
            default:
                {
                    Exception ex = new Exception("Line number = 46");
                    throw ex;

                }
        }

    }
}

Вышеприведенный код имеет следующий вывод в режиме отладки:

arg = 0
System.Exception: Line number = 36
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 40
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 14
arg = 1
System.Exception: Line number = 41
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 45
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 14
arg = 2
System.Exception: Line number = 46
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 47
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 14

В режиме деблокирования второе исключение выбрано из строки 46 вместо 45. Это поведение совместимо для всех версий платформы .NET и всех версий VS. Теперь я попытаюсь сделать это, используя проект VB, чтобы узнать, не имеет значения.

Изменить: используя следующий проект VB в VS 2012:

Module Program
Sub Main()
    Dim test As New Test()
    For i As Integer = 0 To 2
        Try
            test.ThrowSomeException(i)
        Catch exception As Exception
            Console.WriteLine(exception)
        End Try
    Next
End Sub
End Module

Public Class Test
Public Sub ThrowSomeException(arg As Integer)

    Console.WriteLine("arg = {0}", arg)
    Select Case arg
        Case 0
            If True Then
                Dim ex As New Exception("Line number = 22")
                Throw ex
            End If
        Case 1
            If True Then
                Dim ex As New Exception("Line number = 27")
                Throw ex
            End If
        Case Else
            If True Then
                Dim ex As New Exception("Line number = 32")
                Throw ex

            End If
    End Select

End Sub
End Class

Проблема не возникает и номера строк согласованы.

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

arg = 0
System.Exception: Line number = 36
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 40
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 14
arg = 1
System.Exception: Line number = 41
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 45
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 14
arg = 2
System.Exception: Line number = 46
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 47
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 14

И это вывод из режима выпуска exe:

arg = 0
System.Exception: Line number = 36
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 41
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 14
arg = 1
System.Exception: Line number = 41
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 42
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 14
arg = 2
System.Exception: Line number = 46
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 37
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 14

Это не тот же результат, что и использование отладчика для его запуска.

Быстрые тесты, похоже, указывают на то, что дополнительные строки между инструкцией exception и throw (в этом случае консоль .writeline() также влияет на результат:

перезаписан переключатель:

switch (arg)
    {
        case 0:
            {
                Exception ex = new Exception("Line number = 37");
                Console.WriteLine("case 0");
                throw ex;
            }
        case 1:
            {
                Exception ex = new Exception("Line number = 43");
                Console.WriteLine("case 1");
                throw ex;
            }
        default:
            {
                Exception ex = new Exception("Line number = 49");
                Console.WriteLine("case default");
                throw ex;                    
            }
    }

дает этот выход Release из VS:

arg = 0
case 0
System.Exception: Line number = 37
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 43
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 15
arg = 1
case 1
System.Exception: Line number = 43
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 49
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 15
arg = 2
case default
System.Exception: Line number = 49
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 51
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 15

и этот вывод из командной строки:

arg = 0
case 0
System.Exception: Line number = 37
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 43
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 15
arg = 1
case 1
System.Exception: Line number = 43
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 45
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 15
arg = 2
case default
System.Exception: Line number = 49
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 39
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 15

и этот отладочный вывод VS:

arg = 0
case 0
System.Exception: Line number = 37
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 42
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 15
arg = 1
case 1
System.Exception: Line number = 43
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 48
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 15
arg = 2
case default
System.Exception: Line number = 49
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 51
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 15

И этот вывод из командной строки:

arg = 0
case 0
System.Exception: Line number = 37
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 42
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 15
arg = 1
case 1
System.Exception: Line number = 43
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 48
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 15
arg = 2
case default
System.Exception: Line number = 49
   bij linenumbertest.Test.ThrowSomeException(Int32 arg) in c:\Users\nate\Docume
nts\Visual Studio 2012\Projects\linenumbertest\Program.cs:regel 51
   bij linenumbertest.Program.Main() in c:\Users\nate\Documents\Visual Studio 20
12\Projects\linenumbertest\Program.cs:regel 15