关于c#:“throw”和“throw ex”之间有区别吗?

Is there a difference between “throw” and “throw ex”?

有一些帖子问这两者之间已经有什么区别了。
(我为什么还要提到这个…)

但我的问题在某种程度上是不同的,我称之为"throw-ex",在另一个错误中是上帝般的处理方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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.
    }
}

如果在Main中使用try & catch,那么我将使用throw;来重新修正错误。但在上述简化的代码中,所有异常都经过HandleException

当在HandleException内部调用时,throw ex;是否与调用throw具有相同的效果?


是的,有区别;

  • throw ex重置堆栈跟踪(因此您的错误似乎来自HandleException)
  • 江户记1〔2〕没有——原来的罪犯将被保留。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    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已经纠正了我)

以下是不同之处的演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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
}

下面是输出:

1
2
3
4
5
6
7
8
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:

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

例外2:

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

它是否仅在调试模式下维护原始stacktrace?


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

考虑这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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;行的注释,则输出为:

1
2
3
4
5
6
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;线,您将得到:

1
2
3
4
5
6
7
8
9
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行。

如果使用第三种选择(包装外部异常),则不会丢失任何信息:

1
2
3
4
5
6
7
8
9
10
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,在简单的情况下使用内部异常感觉有点间接。

在这篇博文中,他们通过调用(通过反射)internal维护方法InternalPreserveStackTrace()来保存行号(try块的行)。但是这样使用反射并不好(有一天.NET框架可能会在没有警告的情况下更改他们的internal成员)。


让我们了解一下throw和throw的区别。我听说在许多.NET面试中,都会问这个常见的问题。

为了概述这两个术语,throw和throw-ex都用于了解异常发生的位置。throw-ex重写异常的堆栈跟踪,而不考虑实际被抛出的位置。

让我们用一个例子来理解。

让我们了解第一次投掷。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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方法中用throw-ex替换throw-ex。如下所示。

enter image description here

throw-ex代码的输出如下。

enter image description here

您可以看到输出中的差异。throw-ex只是忽略前面的所有层次结构,并使用写入throw-ex的行/方法重置堆栈跟踪。


当您确实抛出ex时,抛出的异常将成为"原始"异常。所以所有以前的堆栈跟踪都不存在。

如果您真的抛出了异常,那么异常就沿着这一行往下走,您将得到完整的堆栈跟踪。


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

您可能希望从handleException返回一个布尔值,无论异常是否应重新引发。


msdn代表:

Once an exception is thrown, part of the information it carries is the
stack trace. The stack trace is a list of the method call hierarchy
that starts with the method that throws the exception and ends with
the method that catches the exception. If an exception is re-thrown by
specifying the exception in the throw statement, the stack trace is
restarted at the current method and the list of method calls between
the original method that threw the exception and the current method is
lost. To keep the original stack trace information with the exception,
use the throw statement without specifying the exception.


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

投掷:

1
2
3
4
5
6
7
8
9
try
{
    // do some operation that can fail
}
catch (Exception ex)
{
    // do some local cleanup
    throw;
}

它保留堆栈信息,但有例外

这被称为"回潮"

如果想抛出新的异常,

1
throw new ApplicationException("operation failed!");

投掷EX:

1
2
3
4
5
6
7
8
9
try
{
    // do some operation that can fail
}
catch (Exception ex)
{
    // do some local cleanup
    throw ex;
}

它不会发送堆栈信息,异常

这被称为"打破堆栈"

如果想抛出新的异常,

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

为了给您一个不同的视角,如果您向客户机提供一个API,并且想要为内部库提供详细的堆栈跟踪信息,那么使用throw特别有用。通过在这里使用throw,我将得到在本例中用于file.delete的system.io.file库的堆栈跟踪。如果我使用throw-ex,那么该信息将不会传递给我的处理程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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都有注释-输出-内部Ex

  • 如果第2行和第3行都有注释-输出-内部ExSystem.DevideByzeroException:"试图除以零。"------

  • 如果第1行和第2行都有注释-输出-内部ExSystem.Exception:偏差为0----

  • 如果第1行和第3行都有注释-输出-内部ExSystem.DevideByzeroException:"试图除以零。"------

  • 如果出现throw-ex,stacktrace将被重置;