'throw'和'throw new Exception()'之间的区别

216

什么是两者之间的区别?

try { ... }
catch{ throw }

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

不管第二个显示了什么信息。


71
第二段是我见过的最邪恶(但看似无害)的代码之一。 - SLaks
https://dev59.com/0nVD5IYBdhLWcg3wTZxm - dotjoe
1
这回答了你的问题吗?“throw”和“throw ex”有什么区别? - Jim G.
12个回答

353

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

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


永远不要编写 throw ex;


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

在某些情况下,您可能希望将所有异常包装在自定义异常对象中,以便您可以提供关于代码在抛出异常时正在执行的其他信息。

为此,请定义一个继承Exception的新类,添加所有四个异常构造函数,以及可选地一个接受InnerException以及其他信息的构造函数,并抛出您的新异常类,ex作为InnerException参数传递即可。通过传递原始的InnerException,您可以保留所有原始异常的属性,包括堆栈跟踪。


26
“throw new Exception(ex); is even worse.”的意思是“抛出新异常(throw new Exception(ex);)甚至更糟糕。”我在这个问题上持不同意见。有时候你想要改变异常的类型,然后将原始异常保留为内部异常是你所能做的最好的事情。当然,应该使用throw new MyCustomException(myMessage, ex); - Dirk Vollmar
11
我明白,我将为您翻译:@0xA3: 我指的是ex.Message,这实际上更糟糕。 Translated: I understand, I will translate for you: @0xA3: I meant ex.Message, which is worse. - SLaks
6
除了实现标准构造函数之外,还应该创建自定义异常 [Serializable()] - Dirk Vollmar
27
嘿,兄弟,我们听说你喜欢异常,所以我们在你的异常里加了一个异常,这样你就可以在捕获时进行捕获。 - Darth Continent
4
当您使用 throw; 语句时,实际引发异常的代码行号会被替换为 throw; 所在的代码行号。您认为应该如何处理这种情况?(原问题链接:https://dev59.com/tnE95IYBdhLWcg3wCJNf) - Eric J.
显示剩余8条评论

46

第一个保留原始的堆栈跟踪信息:

try { ... }
catch
{
    // Do something.
    throw;
}
第二种方法允许您更改异常的类型和/或消息和其他数据:
try { ... } catch (Exception e)
{
    throw new BarException("Something broke!");
}

还有一种方法是通过传递内部异常来处理:

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

我建议使用:

  • 如果您想在出现错误的情况下进行清理而不破坏信息或添加有关错误的信息,则使用第一种方法。
  • 如果您想添加有关错误的更多信息,则使用第三种方法。
  • 如果您想隐藏信息(以防止未经信任的用户看到),则使用第二种方法。

14

这里的回答没有展示出差别,对于那些努力理解差异的人可能会有所帮助。考虑以下示例代码:

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());
            }
         }
      }
   }
}

它会生成以下输出:

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字典中添加信息),同时保留原始异常中的所有信息(包括帮助链接、数据字典等)。


1
不确定为什么这篇回答被投票否决却没有任何评论,因为它基本上与顶部的答案说的是一样的,只是更详细。 - Lee

14

还有一点是我没看到其他任何人提到过的:

如果在catch {}块中什么也不做,那使用try...catch就没有意义了。我经常看到类似这样的代码:

try 
{
  //Code here
}
catch
{
    throw;
}

或者更糟糕:

try 
{
  //Code here
}
catch(Exception ex)
{
    throw ex;
}

更糟糕的是:

try 
{
  //Code here
}
catch(Exception ex)
{
    throw new System.Exception(ex.Message);
}

我同意,除非你有finally子句。 - Toni Rossmann
4
在这种情况下,我会使用try..finally而不使用catch,除非你正在做除throw之外的其他事情。 - John Warlow

10

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

throw;会保留原始的堆栈跟踪,通常更加有用。唯一例外的情况是当你想要在自定义异常中包装异常时。那么你应该这样做:

catch(Exception e)
{
    throw new CustomException(customMessage, e);
}

4

throw语句重新抛出已捕获的异常,保留堆栈跟踪信息,而throw new Exception会丢失某些已捕获异常的细节。

通常情况下,您可以仅使用throw语句记录异常,而不必在此时完全处理它。

BlackWasp有一篇名为在C#中抛出异常的好文章。


3

throw用于重新抛出已捕获的异常。如果您想在将异常传递到调用链之前对其进行某些操作,则可以使用此功能。

如果没有任何参数使用throw,则保留调用堆栈以进行调试。


1
你的第二个示例将重置异常的堆栈跟踪。第一个最准确地保留了异常的起源。
同时,你已经解包了原始类型,这对于了解实际出了什么问题很关键... 如果第二个示例对功能是必需的--例如,添加扩展信息或重新包装为特殊类型,如自定义的'HandleableException',请确保也设置了InnerException属性!

是的,这是那种你必须快速写作的问题。 ;) - Robert Harvey

1

Throw;: 重新抛出原始异常并保留异常类型。

Throw new exception();: 重新抛出原始异常类型并重置异常堆栈跟踪。

Throw ex;: 重置异常堆栈跟踪并重置异常类型。


0

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

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] catch块捕获。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接