“扔”和“扔”有区别吗?“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 & catchMain中使用,那么我将使用throw;重新抛出错误。 但在上面的简单代码中,所有异常都经过HandleException

throw ex;是否与在HandleException中调用throw具有相同的效果?

254763 次浏览

是的,这是有区别的。

  • 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;
    }
    }
    

不,这将导致异常具有不同的堆栈跟踪。只有在catch处理程序中使用throw而不使用任何异常对象,才会使堆栈跟踪保持不变。

你可能想从HandleException返回一个布尔值,不管是否应该重新抛出异常。

当您执行throw ex时,抛出的异常将成为“原始”异常。因此所有之前的堆栈跟踪都不会在那里。

如果执行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 ?

其他答案完全正确,但我认为这个答案提供了一些额外的细节。

想想这个例子:

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方法导致了问题。但是仍然很难看出这个问题是来自try块中的第35行还是第37行。

如果你使用第三种选择,在一个外部异常中包装,你不会丢失任何信息:

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,在简单的情况下使用内部异常感觉有点间接。

这篇博文中,它们通过在Exception对象上调用(通过反射)internal实例方法InternalPreserveStackTrace()来保留行号(try块的行)。但是像这样使用反射并不好(. net Framework可能会在没有警告的情况下改变它们的internal成员)。

看这里:http://blog-mstechnology.blogspot.de/2010/06/throw-vs-throw-ex.html

# EYZ0:

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

它保留了异常堆栈信息

这叫做"重扔"

如果想要抛出新的异常,

throw new ApplicationException("operation failed!");

# EYZ0:

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

它不会发送异常堆栈信息

这叫做“打破堆栈”

如果想要抛出新的异常,

throw new ApplicationException("operation failed!",ex);
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行都注释了- Output - inner ex

  2. 如果所有第2行和第3行都被注释- 输出-内ex 系统。DevideByZeroException:{"试图除零。"}--------- < / >

  3. 如果所有第1行和第2行都被注释- 输出-内ex 系统。例外:除0 ----

  4. 如果所有第1行和第3行都被注释- 输出-内ex 系统。DevideByZeroException:{"试图除零。"}--------- < / >

和StackTrace将重置的情况下抛出ex;

为了让您从不同的角度来看待这个问题,如果您向客户端提供API,并且希望为内部库提供详细的堆栈跟踪信息,则使用throw特别有用。通过在这里使用throw,我可以获得File.Delete的System.IO.File库的堆栈跟踪。如果我使用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和throw ex之间的区别。我听说在很多。net面试中这个常见的问题被问到。

简单介绍一下这两个术语,throw和throw 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。还有行号

enter image description here

其次……让我们理解throw ex。只需在M2方法catch块中将throw替换为throw ex。如下。

enter image description here

throw ex代码的输出如下。

enter image description here

你可以在输出中看到差异。Throw ex只是忽略了之前的所有层次结构,并使用写入Throw ex的line/method重置堆栈跟踪。

# EYZ0:

一旦抛出异常,它所携带的部分信息就是堆栈跟踪。堆栈跟踪是方法调用层次结构的列表,从抛出异常的方法开始,以捕获异常的方法结束。如果通过在throw语句中指定异常而重新抛出异常,则堆栈跟踪将在当前方法处重新启动,并且将丢失抛出异常的原始方法与当前方法之间的方法调用列表。要保留原始的堆栈跟踪信息和异常,请使用throw语句而不指定异常。

来源:# EYZ0

最好使用throw而不是throw ex

Throw ex重置原始堆栈跟踪,无法找到之前的堆栈跟踪。

如果使用throw,我们将得到一个完整的堆栈跟踪。

保留堆栈跟踪。假设Source1抛出Error1,它被Source2捕获,而Source2表示抛出,那么在堆栈跟踪中Source1 Error + Source2 Error将可用。

把前女友不保留堆栈跟踪。因此Source1的所有错误将被清除,只有Source2的错误将被发送到客户端。

有时只是阅读的东西不清楚,会建议看这个视频演示更清楚,c#中的Throw vs Throw ex

Throw vs Throw ex

为了扩展Lucero的回答,下面介绍如何在不丢失原始堆栈跟踪的情况下实现原始代码的意图。

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.
}
}