关于c#:throw和throw new之间的区别Exception()


difference between throw and throw new Exception()

两者有什么区别

1
2
try { ... }
catch{ throw }

1
2
try{ ... }
catch(Exception e) {throw new Exception(e.message) }

不管第二个显示什么信息?


throw;重新引发原始异常并保留其原始堆栈跟踪。

throw ex;抛出原始异常,但重置堆栈跟踪,销毁所有堆栈跟踪信息,直到catch块。

< BR>

不写throw ex;

< BR>

throw new Exception(ex.Message);更糟。它创建了一个全新的Exception实例,丢失了异常的原始堆栈跟踪及其类型。(例如,IOException)。此外,有些例外情况还包含其他信息(如ArgumentException.ParamName)。throw new Exception(ex.Message);也会破坏这些信息。

在某些情况下,您可能希望将所有异常包装在一个自定义异常对象中,这样您就可以提供有关在引发异常时代码在做什么的附加信息。

为此,定义一个继承Exception的新类,添加所有四个异常构造函数,并可以选择一个额外的构造函数,它接受InnerException和附加信息,并抛出新的异常类,将ex作为InnerException参数传递。通过传递原始InnerException,可以保留原始异常的所有属性,包括堆栈跟踪。


第一个保留原始stacktrace:

1
2
3
4
5
6
try { ... }
catch
{
    // Do something.
    throw;
}

第二个允许您更改异常的类型和/或消息和其他数据:

1
2
3
4
try { ... } catch (Exception e)
{
    throw new BarException("Something broke!");
}

还有第三种方法可以通过内部异常:

1
2
3
4
try { ... }
catch (FooException e) {
    throw new BarException("foo", e);
}

我建议使用:

  • 第一种方法是在错误情况下进行一些清理,而不破坏信息或添加有关错误的信息。
  • 第三个,如果您想添加关于错误的更多信息。
  • 第二个,如果你想隐藏信息(对不受信任的用户)。


还有一点我没看到有人说:

如果您在catch块中没有做任何事情,尝试…catch是没有意义的。我一直看到这个:

1
2
3
4
5
6
7
8
try
{
  //Code here
}
catch
{
    throw;
}

或更糟:

1
2
3
4
5
6
7
8
try
{
  //Code here
}
catch(Exception ex)
{
    throw ex;
}

最糟糕的是:

1
2
3
4
5
6
7
8
try
{
  //Code here
}
catch(Exception ex)
{
    throw new System.Exception(ex.Message);
}


抛出一个新的异常将清除当前堆栈跟踪。

throw;将保留原始堆栈跟踪,并且几乎总是更有用。该规则的例外情况是,当您希望将异常包装在自己的自定义异常中时。然后你应该做:

1
2
3
4
catch(Exception e)
{
    throw new CustomException(customMessage, e);
}

throw重新抛出捕获的异常,保留堆栈跟踪,而throw new Exception丢失捕获异常的一些细节。

通常情况下,您会单独使用throw来记录一个异常,而不在此时完全处理它。

Blackwasp有一篇很好的文章,题为《在C中抛出异常》。


throw用于重新引发捕获的异常。如果您想在将异常传递到调用链之前对其进行处理,这将非常有用。

在不带任何参数的情况下使用throw,可以保留调用堆栈以进行调试。


引发;重新引发原始异常并保留异常类型。

throw new exception();重新引发原始异常类型并重置异常堆栈跟踪

throw ex;重置异常堆栈跟踪并重置异常类型


throw或throw-ex都用于抛出或重新引发异常,当您只需记录错误信息,不想将任何信息发送回调用方时,只需将错误记录在catch中并离开。但是,如果您想向使用throw或throw-ex的调用者发送一些关于异常的有意义的信息,那么throw和throw-ex的区别在于throw保留了堆栈跟踪和其他信息,而throw-ex创建了一个新的异常对象,因此原始的堆栈跟踪将丢失。所以当我们应该使用throw和throw e时,仍然有一些情况下您可能希望重新引发异常,比如重置调用堆栈信息。例如,如果该方法位于库中,并且您希望从调用代码中隐藏库的详细信息,则不一定希望调用堆栈包含库中私有方法的信息。在这种情况下,您可以捕获库的公共方法中的异常,然后重新执行这些异常,以便调用堆栈从这些公共方法开始。


最重要的区别是第二个表达式删除异常类型。异常类型在捕获异常中起着至关重要的作用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void MyMethod ()
{
    // both can throw IOException
    try { foo(); } catch { throw; }
    try { bar(); } catch(E) {throw new Exception(E.message); }
}

(...)

try {
    MyMethod ();
} catch (IOException ex) {
    Console.WriteLine ("Error with I/O"); // [1]
} catch (Exception ex) {
    Console.WriteLine ("Other error");    // [2]
}

如果foo()抛出IOException[1]catch块将捕获异常。但当bar()抛出IOException时,它将被转换成普通的Exception蚂蚁不会被[1]捕获块捕获。


第二个示例将重置异常的堆栈跟踪。第一个最准确地保留了异常的起源。另外,你还打开了原始类型,这是知道实际出了什么问题的关键…如果功能需要第二个-例如,添加扩展信息或使用特殊类型(如自定义"handleableexception")重新包装,则只需确保也设置了InnerException属性!


如果需要,您可以抛出一个新的异常,将原来的异常设置为内部异常。


这里的答案没有一个显示出差异,这可能有助于人们努力理解差异。考虑这个示例代码:

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
using System;
using System.Collections.Generic;

namespace ExceptionDemo
{
   class Program
   {
      static void Main(string[] args)
      {
         void fail()
         {
            (null as string).Trim();
         }

         void bareThrow()
         {
            try
            {
               fail();
            }
            catch (Exception e)
            {
               throw;
            }
         }

         void rethrow()
         {
            try
            {
               fail();
            }
            catch (Exception e)
            {
               throw e;
            }
         }

         void innerThrow()
         {
            try
            {
               fail();
            }
            catch (Exception e)
            {
               throw new Exception("outer", e);
            }
         }

         var cases = new Dictionary<string, Action>()
         {
            {"Bare Throw:", bareThrow },
            {"Rethrow", rethrow },
            {"Inner Throw", innerThrow }
         };

         foreach (var c in cases)
         {
            Console.WriteLine(c.Key);
            Console.WriteLine(new string('-', 40));
            try
            {
               c.Value();
            } catch (Exception e)
            {
               Console.WriteLine(e.ToString());
            }
         }
      }
   }
}

产生以下输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Bare Throw:
----------------------------------------
System.NullReferenceException: Object reference not set to an instance of an object.
   at ExceptionDemo.Program.<Main>g__fail|0_0() in C:\...\ExceptionDemo\Program.cs:line 12
   at ExceptionDemo.Program.<>c.<Main>g__bareThrow|0_1() in C:\...\ExceptionDemo\Program.cs:line 19
   at ExceptionDemo.Program.Main(String[] args) in C:\...\ExceptionDemo\Program.cs:line 64

Rethrow
----------------------------------------
System.NullReferenceException: Object reference not set to an instance of an object.
   at ExceptionDemo.Program.<>c.<Main>g__rethrow|0_2() in C:\...\ExceptionDemo\Program.cs:line 35
   at ExceptionDemo.Program.Main(String[] args) in C:\...\ExceptionDemo\Program.cs:line 64

Inner Throw
----------------------------------------
System.Exception: outer ---> System.NullReferenceException: Object reference not set to an instance of an object.
   at ExceptionDemo.Program.<Main>g__fail|0_0() in C:\...\ExceptionDemo\Program.cs:line 12
   at ExceptionDemo.Program.<>c.<Main>g__innerThrow|0_3() in C:\...\ExceptionDemo\Program.cs:line 43
   --- End of inner exception stack trace ---
   at ExceptionDemo.Program.<>c.<Main>g__innerThrow|0_3() in C:\...\ExceptionDemo\Program.cs:line 47
   at ExceptionDemo.Program.Main(String[] args) in C:\...\ExceptionDemo\Program.cs:line 64

如前面的答案所示,裸抛出清楚地显示了失败的原始代码行(第12行),以及异常发生时调用堆栈中的其他两个活动点(第19行和第64行)。

重新抛出案例的输出显示了问题的原因。当异常像这样重新引发时,异常将不包括原始堆栈信息。请注意,只包括throw e(第35行)和最外面的调用堆栈点(第64行)。如果以这种方式抛出异常,将很难找到fail()方法作为问题的根源。

最后一个案例(innerthrow)是最详细的,包含的信息比上述任何一个都多。因为我们正在实例化一个新的异常,所以我们有机会添加上下文信息(这里是"外部"消息,但我们也可以在新异常上添加到.data字典),并保留原始异常中的所有信息(包括帮助链接、数据字典等)。