Есть ли разница между «throw» и «throw ex»?


437

Есть несколько постов, которые спрашивают, какая разница между этими двумя уже есть.
(почему я должен даже упомянуть об этом ...)

Но мой вопрос отличается от того, что я называю «throw ex» в другом богоподобном методе обработки ошибок.

public class Program {
    public static void Main(string[] args) {
        try {
            // something
        } catch (Exception ex) {
            HandleException(ex);
        }
    }

    private static void HandleException(Exception ex) {
        if (ex is ThreadAbortException) {
            // ignore then,
            return;
        }
        if (ex is ArgumentOutOfRangeException) { 
            // Log then,
            throw ex;
        }
        if (ex is InvalidOperationException) {
            // Show message then,
            throw ex;
        }
        // and so on.
    }
}

Если бы try & catchбыли использованы в Main, то я бы использовал, throw;чтобы сбросить ошибку. Но в приведенном выше упрощенном коде все исключения проходятHandleException

Имеет ли throw ex;тот же эффект, что и вызов, throwкогда вызывается изнутри HandleException?


3
Есть разница, это связано с тем, появляется ли или как трассировка стека в исключении, но я не помню, какой именно сейчас, поэтому я не буду перечислять этот ответ.
Джоэл Коухорн

@Joel: спасибо. Я предполагаю, что использование исключения HandleError - плохая идея. Я просто хотел реорганизовать код обработки ошибок.
dance2die

1
Третий способ - завернуть в новое исключение и перебросить timwise.blogspot.co.uk/2014/05/…
Тим Абелл

Ответы:


680

Да, есть разница;

  • throw exсбрасывает трассировку стека (так что ваши ошибки могут появиться HandleException)
  • throw нет - первоначальный преступник будет сохранен.

    static void Main(string[] args)
    {
        try
        {
            Method2();
        }
        catch (Exception ex)
        {
            Console.Write(ex.StackTrace.ToString());
            Console.ReadKey();
        }
    }
    
    private static void Method2()
    {
        try
        {
            Method1();
        }
        catch (Exception ex)
        {
            //throw ex resets the stack trace Coming from Method 1 and propogates it to the caller(Main)
            throw ex;
        }
    }
    
    private static void Method1()
    {
        try
        {
            throw new Exception("Inside Method1");
        }
        catch (Exception)
        {
            throw;
        }
    }

28
Чтобы немного расширить ответ Марка, вы можете найти более подробную информацию здесь: geekswithblogs.net/sdorman/archive/2007/08/20/…
Скотт Дорман,

3
@Shaul; нет не Я дал подробности в комментарии к вашему посту.
Марк Гравелл

1
@Marc Gravell - мои извинения, вы были правы. Извините за понижение; слишком поздно для меня, чтобы отменить ... :(
Shaul Behr

3
@Marc: Кажется, что throw сохраняет первоначального нарушителя ТОЛЬКО в том случае, если бросок не входит в метод, в котором было сгенерировано первоначальное исключение (см. Этот вопрос: stackoverflow.com/questions/5152265/… )
Бранн

3
@ScottDorman Похоже, ваша ссылка неправильно пересылается после переноса блога. Похоже, сейчас он живет здесь . Редактировать: Эй, подождите, это ваш блог! Исправьте свои собственные ссылки! ; ^ D
Ерш

96

(Я отправил ранее, и @Marc Gravell исправил меня)

Вот демонстрация разницы:

static void Main(string[] args) {
    try {
        ThrowException1(); // line 19
    } catch (Exception x) {
        Console.WriteLine("Exception 1:");
        Console.WriteLine(x.StackTrace);
    }
    try {
        ThrowException2(); // line 25
    } catch (Exception x) {
        Console.WriteLine("Exception 2:");
        Console.WriteLine(x.StackTrace);
    }
}

private static void ThrowException1() {
    try {
        DivByZero(); // line 34
    } catch {
        throw; // line 36
    }
}
private static void ThrowException2() {
    try {
        DivByZero(); // line 41
    } catch (Exception ex) {
        throw ex; // line 43
    }
}

private static void DivByZero() {
    int x = 0;
    int y = 1 / x; // line 49
}

и вот вывод:

Exception 1:
   at UnitTester.Program.DivByZero() in <snip>\Dev\UnitTester\Program.cs:line 49
   at UnitTester.Program.ThrowException1() in <snip>\Dev\UnitTester\Program.cs:line 36
   at UnitTester.Program.TestExceptions() in <snip>\Dev\UnitTester\Program.cs:line 19

Exception 2:
   at UnitTester.Program.ThrowException2() in <snip>\Dev\UnitTester\Program.cs:line 43
   at UnitTester.Program.TestExceptions() in <snip>\Dev\UnitTester\Program.cs:line 25

Вы можете видеть, что в Исключении 1 трассировка стека возвращается к DivByZero()методу, тогда как в Исключении 2 это не так.

Обратите внимание, однако, что номер строки, показанный в ThrowException1()и ThrowException2()является номером строки throwоператора, а не номером строки вызова DivByZero(), что, вероятно, имеет смысл сейчас, когда я немного об этом подумаю ...

Выход в режиме выпуска

Исключение 1:

at ConsoleAppBasics.Program.ThrowException1()
at ConsoleAppBasics.Program.Main(String[] args)

Исключение 2:

at ConsoleAppBasics.Program.ThrowException2()
at ConsoleAppBasics.Program.Main(String[] args)

Поддерживает ли исходный stackTrace только в режиме отладки?


1
Это потому, что процесс оптимизации компилятора включает короткие методы, такие как DevideByZero, поэтому трассировка стека одинакова. может быть, вы должны опубликовать это как вопрос сам по себе
Менахем

42

Другие ответы совершенно верны, но я думаю, что этот ответ дает некоторые дополнительные детали.

Рассмотрим этот пример:

using System;

static class Program {
  static void Main() {
    try {
      ThrowTest();
    } catch (Exception e) {
      Console.WriteLine("Your stack trace:");
      Console.WriteLine(e.StackTrace);
      Console.WriteLine();
      if (e.InnerException == null) {
        Console.WriteLine("No inner exception.");
      } else {
        Console.WriteLine("Stack trace of your inner exception:");
        Console.WriteLine(e.InnerException.StackTrace);
      }
    }
  }

  static void ThrowTest() {
    decimal a = 1m;
    decimal b = 0m;
    try {
      Mult(a, b);  // line 34
      Div(a, b);   // line 35
      Mult(b, a);  // line 36
      Div(b, a);   // line 37
    } catch (ArithmeticException arithExc) {
      Console.WriteLine("Handling a {0}.", arithExc.GetType().Name);

      //   uncomment EITHER
      //throw arithExc;
      //   OR
      //throw;
      //   OR
      //throw new Exception("We handled and wrapped your exception", arithExc);
    }
  }

  static void Mult(decimal x, decimal y) {
    decimal.Multiply(x, y);
  }
  static void Div(decimal x, decimal y) {
    decimal.Divide(x, y);
  }
}

Если вы раскомментируете throw arithExc;строку, вы получите:

Handling a DivideByZeroException.
Your stack trace:
   at Program.ThrowTest() in c:\somepath\Program.cs:line 44
   at Program.Main() in c:\somepath\Program.cs:line 9

No inner exception.

Конечно, вы потеряли информацию о том, где произошло это исключение. Если вместо этого вы используете throw;строку, это то, что вы получите:

Handling a DivideByZeroException.
Your stack trace:
   at System.Decimal.FCallDivide(Decimal& d1, Decimal& d2)
   at System.Decimal.Divide(Decimal d1, Decimal d2)
   at Program.Div(Decimal x, Decimal y) in c:\somepath\Program.cs:line 58
   at Program.ThrowTest() in c:\somepath\Program.cs:line 46
   at Program.Main() in c:\somepath\Program.cs:line 9

No inner exception.

Это намного лучше, потому что теперь вы видите, что именно этот Program.Divметод вызывал у вас проблемы. Но все еще трудно понять, возникает ли эта проблема из строки 35 или строки 37 в tryблоке.

Если вы используете третий вариант, заключая внешнее исключение, вы не потеряете информацию:

Handling a DivideByZeroException.
Your stack trace:
   at Program.ThrowTest() in c:\somepath\Program.cs:line 48
   at Program.Main() in c:\somepath\Program.cs:line 9

Stack trace of your inner exception:
   at System.Decimal.FCallDivide(Decimal& d1, Decimal& d2)
   at System.Decimal.Divide(Decimal d1, Decimal d2)
   at Program.Div(Decimal x, Decimal y) in c:\somepath\Program.cs:line 58
   at Program.ThrowTest() in c:\somepath\Program.cs:line 35

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

В этом блоге они сохраняют номер строки (строки блока TRY) по телефону (посредством отражения) по internalметоде входа в инстансе InternalPreserveStackTrace()на Exceptionобъекте. Но не очень хорошо использовать такое отражение (.NET Framework может изменить своих internalчленов однажды без предупреждения).


6

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

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

Давайте разберемся с примером.

Давайте разберемся первым броском.

static void Main(string[] args) {
    try {
        M1();
    } catch (Exception ex) {
        Console.WriteLine(" -----------------Stack Trace Hierarchy -----------------");
        Console.WriteLine(ex.StackTrace.ToString());
        Console.WriteLine(" ---------------- Method Name / Target Site -------------- ");
        Console.WriteLine(ex.TargetSite.ToString());
    }
    Console.ReadKey();
}

static void M1() {
    try {
        M2();
    } catch (Exception ex) {
        throw;
    };
}

static void M2() {
    throw new DivideByZeroException();
}

выход вышеизложенного ниже.

показывает полную иерархию и имя метода, где на самом деле сгенерировано исключение .. это M2 -> M2. вместе с номерами строк

введите описание изображения здесь

Во-вторых .. давайте разберемся, бросив экс. Просто замените throw на throw ex в блоке catch метода M2. как ниже.

введите описание изображения здесь

вывод команды throw ex code показан ниже.

введите описание изображения здесь

Вы можете увидеть разницу в выходных данных. Throw ex просто игнорирует всю предыдущую иерархию и сбрасывает трассировку стека с помощью строки / метода, в которую записывается throw ex.


5

Когда вы это делаете throw ex, то выброшенное исключение становится «оригинальным». Таким образом, все предыдущие трассировки стека не будут там.

Если вы это сделаете throw, исключение просто пойдет по линии, и вы получите полную трассировку стека.


4

Нет, это приведет к тому, что у исключения будет другая трассировка стека. Только используя throwбез исключения объект вcatch обработчике оставит трассировку стека без изменений.

Вы можете захотеть вернуть логическое значение из HandleException, независимо от того, будет ли исключение переброшено или нет.


4

MSDN означает :

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


2

Посмотрите здесь: http://blog-mstechnology.blogspot.de/2010/06/throw-vs-throw-ex.html

Бросить :

try 
{
    // do some operation that can fail
}
catch (Exception ex)
{
    // do some local cleanup
    throw;
}

Это сохраняет информацию стека с исключением

Это называется "Ретроу"

Если хотите бросить новое исключение,

throw new ApplicationException("operation failed!");

Брось Ex :

try
{
    // do some operation that can fail
}
catch (Exception ex)
{
    // do some local cleanup
    throw ex;
}

Это не отправит информацию о стеке с исключением

Это называется «Разбить стек»

Если хотите бросить новое исключение,

throw new ApplicationException("operation failed!",ex);

0

Чтобы дать вам другое представление об этом, использование throw особенно полезно, если вы предоставляете клиенту API и хотите предоставить подробную информацию трассировки стека для своей внутренней библиотеки. Используя здесь throw, я бы получил трассировку стека в этом случае библиотеки System.IO.File для File.Delete. Если я использую throw ex, то эта информация не будет передана моему обработчику.

static void Main(string[] args) {            
   Method1();            
}

static void Method1() {
    try {
        Method2();
    } catch (Exception ex) {
        Console.WriteLine("Exception in Method1");             
    }
}

static void Method2() {
    try {
        Method3();
    } catch (Exception ex) {
        Console.WriteLine("Exception in Method2");
        Console.WriteLine(ex.TargetSite);
        Console.WriteLine(ex.StackTrace);
        Console.WriteLine(ex.GetType().ToString());
    }
}

static void Method3() {
    Method4();
}

static void Method4() {
    try {
        System.IO.File.Delete("");
    } catch (Exception ex) {
        // Displays entire stack trace into the .NET 
        // or custom library to Method2() where exception handled
        // If you want to be able to get the most verbose stack trace
        // into the internals of the library you're calling
        throw;                
        // throw ex;
        // Display the stack trace from Method4() to Method2() where exception handled
    }
}

-1
int a = 0;
try {
    int x = 4;
    int y ;
    try {
        y = x / a;
    } catch (Exception e) {
        Console.WriteLine("inner ex");
        //throw;   // Line 1
        //throw e;   // Line 2
        //throw new Exception("devide by 0");  // Line 3
    }
} catch (Exception ex) {
    Console.WriteLine(ex);
    throw ex;
}
  1. если все строки 1, 2 и 3 прокомментированы - Выход - внутренний экс

  2. если все строки 2 и 3 закомментированы - Выходные данные - внутренняя от System.DevideByZeroException: {"Попытка деления на ноль."} ---------

  3. если все строки 1 и 2 прокомментированы - Выход - внутренний ex System.Exception: делится на 0 ----

  4. если все строки 1 и 3 закомментированы - Выход - внутренний ex System.DevideByZeroException: {"Попытка деления на ноль."} ---------

и StackTrace будет сброшен в случае броска ex;

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.