Почему "бросить" и "выбросить ex" в блокирующий блок ведут себя одинаково?

Я читал, что когда в блоке catch я могу перебросить текущее исключение, используя "throw"; или "выбросить";

От: http://msdn.microsoft.com/en-us/library/ms182363%28VS.80%29.aspx

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

Но когда я пытаюсь это сделать

        try{
            try{
                try{
                    throw new Exception("test"); // 13
                }catch (Exception ex1){
                    Console.WriteLine(ex1.ToString());
                    throw; // 16
                }
            }catch (Exception ex2){
                Console.WriteLine(ex2.ToString()); // expected same stack trace
                throw ex2; // 20
            }
        }catch (Exception ex3){
            Console.WriteLine(ex3.ToString());
        }

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

System.Exception: test at ConsoleApplication1.Program.Main(String [] args) в c:\Program.cs: строка 13 System.Exception: test at ConsoleApplication1.Program.Main(String [] args) в c:\Program. cs: строка 16 System.Exception: test at ConsoleApplication1.Program.Main(String [] args) в c:\Program.cs: строка 20

Ответ 1

throw сохранит только стек стека, если вы не выбросите его из текущего. Вы делаете именно это, делая все это одним способом.

См. Этот ответ: fooobar.com/questions/21201/...

PS: +1 для того, чтобы задать вопрос, который действительно был действительным вопросом.

Ответ 2

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

static void Main(string[] args)
{
    try
    {
        try
        {
            try
            {
                Foo();
            }
            catch (Exception ex1)
            {
                Console.WriteLine(ex1.ToString());
                throw;
            }
        }
        catch (Exception ex2)
        {
            Console.WriteLine(ex2.ToString()); // expected same stack trace
            throw ex2;
        }
    }
    catch (Exception ex3)
    {
        Console.WriteLine(ex3.ToString());
    }
}

static void Foo()
{
    throw new Exception("Test2");
}

Ответ 3

Ну, я немного выкопал эту проблему, и вот мой очень личный вывод:

Никогда не используйте "бросок"; но всегда реконструируйте новое исключение с указанной причиной.

Вот мои рассуждения:

Я был под влиянием моего предыдущего опыта работы с Java и ожидал, что С# throw будет очень похож на Java. Ну, я немного порылся в этом вопросе, и вот мои наблюдения:

    static void Main(string[] args){
        try {
            try {
                throw new Exception("test"); // 13
            }
            catch (Exception ex) {
                Console.WriteLine(ex.ToString());
                throw ex;// 17
            }
        } catch (Exception ex) {
            Console.WriteLine(ex.ToString());
        }
    }

Урожайность:

System.Exception: test
  at ConsoleApplication1.Program.Main(String[] args) in Program.cs:line 13
System.Exception: test
  at ConsoleApplication1.Program.Main(String[] args) in Program.cs:line 17

Интуитивно, Java-программист ожидал, что оба исключения будут одинаковыми:

System.Exception: test
  at ConsoleApplication1.Program.Main(String[] args) in Program.cs:line 13

Но документация на С# ясно говорит, что это то, что следует ожидать:

"Если исключение повторно выбрано, указав исключение в выражении throw, трассировка стека будет перезапущена в текущем методе и список вызовов методов между исходным методом, который забросил исключение, и текущий метод потерян. исходную информацию трассировки стека с исключением, используйте инструкцию throw без указания исключения ".

Теперь, если я немного изменил тест (заменив throw ex; by throw, в строке 17).

        try {
            try {
                throw new Exception("test"); // 13
            }
            catch (Exception ex) {
                Console.WriteLine(ex.ToString());
                throw;// 17
            }
        } catch (Exception ex) {
            Console.WriteLine(ex.ToString());
        }

Урожайность:

System.Exception: test
  at ConsoleApplication1.Program.Main(String[] args) in Program.cs:line 13
System.Exception: test
  at ConsoleApplication1.Program.Main(String[] args) in Program.cs:line 17

Очевидно, это не то, что я ожидал (поскольку это был исходный вопрос). Я потерял исходную точку бросания во второй трассе стека. Саймон Уайтхед связал объяснение, которое является тем броском; сохраняет трассировку стека только в том случае, если исключение не произошло в текущем методе. Таким образом, "throw" без параметра в том же методе довольно бесполезен, поскольку, как правило, это не поможет вам найти причину исключения.

Выполняя то, что сделает любой Java-программист, я заменил оператор в строке 17 на:

throw new Exception("rethrow", ex);// 17

Урожайность:

System.Exception: test
  at ConsoleApplication1.Program.Main(String[] args) in Program.cs:line 13

System.Exception: rethrow ---> System.Exception: test
  at ConsoleApplication1.Program.Main(String[] args) in Program.cs:line 13
  --- End of inner exception stack trace ---
  at ConsoleApplication1.Program.Main(String[] args) in Program.cs:line 17

что является гораздо лучшим результатом.

Затем я начал тестировать вызовы методов.

    private static void throwIt() {
        throw new Exception("Test"); // 10
    }

    private static void rethrow(){
        try{
            throwIt(); // 15
        } catch (Exception ex) {
            Console.WriteLine(ex.ToString());
            throw; // 18
        }
    }

    static void Main(string[] args){
        try{
            rethrow(); // 24
        } catch (Exception ex) {
            Console.WriteLine(ex.ToString());
        }
    }

Опять же, трассировки стека не были тем, что ожидал я (программист Java). В Java оба стека были бы одинаковыми, а три метода были бы такими же:

java.lang.Exception: Test
    at com.example.Test.throwIt(Test.java:10)
    at com.example.Test.rethrow(Test.java:15)
    at com.example.Test.main(Test.java:24)

Первая трассировка стека имеет только два метода.

System.Exception: Test
  at ConsoleApplication1.Program.throwIt() in Program.cs:line 10
  at ConsoleApplication1.Program.rethrow() in Program.cs:line 15

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

Вторая трассировка стека - это три метода, но строка 18 (throw;) появляется в нем.

System.Exception: Test
  at ConsoleApplication1.Program.throwIt() in Program.cs:line 10
  at ConsoleApplication1.Program.rethrow() in Program.cs:line 18
  at ConsoleApplication1.Program.Main(String[] args) in Program.cs:line 24

Это наблюдение аналогично предыдущему: трассировка стека не сохраняется для области текущего метода, и снова я теряю вызываемый метод, в котором происходит исключение. Например, если rethow было написано как:

private static void rethrow(){
    try{
        if (test) 
            throwIt(); // 15
        else 
            throwIt(); // 17
    } catch (Exception ex) {
        Console.WriteLine(ex.ToString());
        throw; // 20
    }
}

Урожайность

System.Exception: Test
  at ConsoleApplication1.Program.throwIt() in Program.cs:line 10
  at ConsoleApplication1.Program.rethrow() in Program.cs:line 20
  at ConsoleApplication1.Program.Main(String[] args) in Program.cs:line 26

Какой вызов throwIt() выбрал Exception? Stack говорит строку 20, так ли это строка 15 или 17?

Решение такое же, как и предыдущее: оберните причину в новом исключении, которое дает:

System.Exception: rethrow ---> System.Exception: Test
  at ConsoleApplication1.Program.throwIt() in Program.cs:line 10
  at ConsoleApplication1.Program.rethrow(Boolean test) in Program.cs:line 17
  --- End of inner exception stack trace ---
  at ConsoleApplication1.Program.rethrow(Boolean test) in Program.cs:line 20
  at ConsoleApplication1.Program.Main(String[] args) in Program.cs:line 26

Мой простой вывод ко всему этому - никогда не использовать "бросок"; но всегда восстанавливать новое исключение с указанной причиной.