有没有真正的世界理由使用throw ex?

时间:2010-12-29 08:51:57

标签: c# .net exception-handling

在C#中,throw ex几乎总是错误的,因为它会重置堆栈跟踪。

我只是想知道,这有什么现实世界吗?我能想到的唯一原因是隐藏你的封闭库的内部,但这是一个非常薄弱的​​原因。除此之外,我在现实世界中从未遇到过。

编辑:我的意思是throw ex,就像投掷完全相同的异常一样,但是有一个空的堆栈跟踪,就像完全错误一样。我知道throw ex必须作为一种语言结构存在,以允许抛出一个不同的异常(throw new DifferentException("ex as innerException", ex)),并且只是想知道是否存在throw ex没有错误的情境。

6 个答案:

答案 0 :(得分:12)

我从未见过它是故意使用的,但这当然没有任何意义。

让我们来看看替代方案:

catch(Exception ex)
{
    // do stuff (logging)
    throw;                                // a) continue ex
    throw new SomeException("blah", ex);  // b) wrap
    throw new SomeException("blah");      // c) replace
    throw ex;                             // d) reset stack-trace
}

c)和d)都放弃了堆栈跟踪,d)唯一的“优势”我可以看到throw ex;保留{{1}的确切类型和可能的额外属性(SQL错误) 1}}。

那么除了堆栈跟踪之外,您是否希望保留异常的所有信息?不正常,但有些猜测:

  • 在模糊库的边界。但是c)在这里看起来更好。
  • 在调用网站上的一些生成代码,就像在RegEx类中一样。

答案 1 :(得分:3)

如果你停下来想一想,throw ex实际上经常被使用 - 除了一个未命名的局部变量。 (重新)设计语言将非常困难:

throw new ArgumentException();

有效,但

var ex = new ArgumentException();
throw ex;

无效(然后扩展它以跟踪更复杂的变量赋值)。即throw ex是throw语句的“正常”形式,但在重新抛出已经捕获的异常时通常是错误的。

答案 2 :(得分:3)

没有正当理由重新抛出异常对象,即'throw ex'。

这是一种语言\编译器功能,尽管在实践中没有增加任何价值,但却没有增加任何价值。这是一种类似的情况,能够编写代码来访问空引用的成员 - 它当然可能但没有价值,例如:

    //Possible but not useful.
    object ref1 = null;

    ref1.ToString();

能够重新抛出异常对象是不幸的,并且经常被新手和有经验的编码人员误解,直到你在生产中得到一个未处理的异常并记录了截断的堆栈跟踪。

有一种微弱的感觉,它可以用来故意隐藏堆栈跟踪信息,但抛出新的异常将是正确的方法。

我甚至可以说能够重新抛出异常对象(即'throw ex')是一个设计缺陷 - 做错事太容易了。但是,我怀疑这是出于性能原因而在编译器中进行设计权衡,因为它会产生开销来验证'throw ex'不是're-throw'。我确信有很多这样的权衡。

答案 3 :(得分:2)

throw ex只是由不知道或忘记throw;的人制作的错误或拼写错误

它可能仅在一种情况下,在其答案中由其他人指出:您不希望将正确的堆栈跟踪发送给调用者的情况,但您仍然希望保留其类型先前抛出的异常。在实践中,我很难想象有人会需要它,并且不会使用throw new SomeCustomException(...)

FxCop规则CA2200 RethrowToPreserveStackDetails采用相同的方式,使用throw;正确邀请您rethrow an exception

try
{
    // ...
}
catch (SomeException ex)
{
    Logger.AddException(ex);
    throw;
}

答案 4 :(得分:0)

以下是重新抛出现有异常对象的两个实际应用程序:

反射

通过反射调用代码时,抛出的任何异常都将包含在TargetInvocationException中。但是你可能想要/需要处理反射“障碍”之外的异常。在这种情况下,包装将使异常处理代码复杂化。相反,您可以解开原始异常并重新抛出。

如果您使用的是.NET 4.5+,则可以使用ExceptionDispatchInfo来保留原始堆栈跟踪:

MethodInfo someMethod = ...
try
{
    someMethod.Invoke();
}
catch (TargetInvocationException e)
{
    ExceptionDispatchInfo.Capture(e.InnerException).Throw();
}

任务API

在.NET Task API中,执行任务期间抛出的异常包含在AggregateException中。当使用async / await时,框架将执行unwrapping,以便抛出原始异常而不是包装器AggregateException。

除了在自动解包时需要重新抛出现有异常的BCL代码之外,有些情况下您无法使用async / await,因此需要手动解包和重新抛出。有关此实例,请查看Stephen Clearys优秀TaskExtensions.WaitAndUnwrapException项目中AsyncContext实施中AsyncEx的内部用法。

答案 5 :(得分:-2)

您可能希望记录异常,记录它然后将其作为'throw ex'抛出到上层。您可能还想在此图层中进行记录,但这可能与您获得异常的图层无关,或者您可能不需要该异常的详细信息,因为您已经记录了它。

让我们说:

Public int GetAllCustomers()
{
     try
     {
          return data.GetAllCustomers()
     }
     catch()
     {
         Logger.log("Error calling GetAllCustomers");
     }
}
     //data
Public static int GetAllCustomers()
{
     try
     {
          //db operations...
     }
     catch(Exception ex)
     {
          Logger.log(ex.Stacktrace);
          throw ex;
     }
}