Есть ли разница между "throw" и "throw ex"?
Есть несколько постов, которые спрашивают, какая разница между этими двумя уже есть.
(почему я должен даже упомянуть об этом...)
Но мой вопрос отличается от того, что я называю "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
?
14 ответов
Да, есть разница;
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; } }
(Я писал ранее, и @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 только в режиме отладки?
Бросок сохраняет трассировку стека. Итак, допустим, Source1 выдает Error1, его перехватывает Source2, а Source2 говорит throw, тогда Source1 Error + Source2 Error будет доступен в трассировке стека.
Throw ex не сохраняет трассировку стека. Таким образом, все ошибки Source1 будут стерты, и клиенту будет отправлена только ошибка Source2.
Иногда просто чтение непонятно, предлагаю посмотреть эту видео-демонстрацию для большей ясности, Throw vs Throw ex в C # .
Другие ответы совершенно правильны, но я думаю, что этот ответ дает некоторые дополнительные детали.
Рассмотрим этот пример:
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
члены когда-нибудь без предупреждения).
Когда вы бросаете ex, то выброшенное исключение становится "оригинальным". Таким образом, все предыдущие трассировки стека не будут там.
Если вы бросите, исключение просто пойдет по линии, и вы получите полную трассировку стека.
Давайте поймем разницу между броском и броском экс. Я слышал, что во многих.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.
MSDN означает:
Как только выдается исключение, часть информации, которую он несет, является трассировкой стека. Трассировка стека представляет собой список иерархии вызовов метода, который начинается с метода, который вызывает исключение, и заканчивается методом, который перехватывает исключение. Если исключение вызывается повторно путем указания исключения в операторе throw, трассировка стека перезапускается в текущем методе, и список вызовов метода между исходным методом, вызвавшим исключение, и текущим методом теряется. Чтобы сохранить исходную информацию трассировки стека с исключением, используйте оператор throw без указания исключения.
Нет, это приведет к тому, что у исключения будет другая трассировка стека. Только используя throw
без каких-либо исключений объекта в catch
Обработчик оставит трассировку стека без изменений.
Вы можете захотеть вернуть логическое значение из HandleException, будет ли исключение переброшено или нет.
Лучше использовать
throw
вместо
throw ex
.
throw ex сбрасывает исходную трассировку стека и не может найти предыдущую трассировку стека.
Если мы используем throw, мы получим полную трассировку стека.
Посмотрите здесь: 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);
Чтобы расширить ответ Лусеро, вот как вы можете выполнить намерение исходного кода, не теряя исходную трассировку стека.
public class Program {
public static void Main(string[] args) {
try {
// something
} catch (Exception ex) {
if (!HandleException(ex)) throw;
}
}
/// <returns>
/// true if the exception has been handled;
/// false if exception should be passed along
/// </returns>
private static bool HandleException(Exception ex) {
if (ex is ThreadAbortException) {
// ignore then,
return true;
}
if (ex is ArgumentOutOfRangeException) {
// Log then,
return false;
}
if (ex is InvalidOperationException) {
// Show message then,
return false;
}
// and so on.
}
}
Чтобы дать вам другое представление об этом, использование 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
}
}
Небольшая поправка ко всем правильным ответам: если вы используете
throw ex
в режиме отладки он покажет полную трассировку стека, как и
throw
. Поэтому, если вы хотите увидеть разницу, измените конфигурацию решения на « Выпуск».
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,2 и 3 прокомментированы - Выход - внутренний экс
если все строки 2 и 3 закомментированы - Выход - внутренний ex System.DevideByZeroException: {"Попытка деления на ноль."}---------
если все строки 1 и 2 прокомментированы - Выход - внутренний ex System.Exception: делится на 0 ----
если все строки 1 и 3 закомментированы - Выходные данные - внутренняя часть ex System.DevideByZeroException: {"Попытка деления на ноль."}---------
и StackTrace будет сброшен в случае броска ex;