【问题标题】:Is there any real world reason to use throw ex?有任何现实世界的理由使用 throw ex 吗?
【发布时间】:2011-05-31 23:03:45
【问题描述】:

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

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

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

【问题讨论】:

  • 你的意思是throw ex;还是throw;?
  • @Andrew 编辑了问题。我的意思是扔前任,只是想知道是否有没有错的情况。
  • 我认为这就是您的意思,但我认为这可能有助于澄清这一点,因为这似乎有些混乱(例如,假设您错误地表示 throw;)
  • 我知道这已经过时了..但这周我只是在想同样的事情。似乎它至少是编译器警告的理想候选者!
  • @MrMoose Damien_The_Unbeliever 的回答说明了为什么不是这样。

标签: c# .net exception-handling


【解决方案1】:

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

让我们看看替代方案:

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; 保留了 ex 的确切类型和可能的额外属性(SQL 错误)。

那么有没有一种情况是您想要保留除堆栈跟踪之外的所有异常信息?不是通常,而是一些猜测:

  • 位于混淆库的边界。但是 c) 在这里看起来是一个更好的选择。
  • 在调用站点对某些生成的代码,例如在 RegEx 类中。

【讨论】:

  • 我只能说,如果你要杀死堆栈跟踪以保护你的库 IP,你最好给我一个非常好的异常消息。 :)
  • @Paul:是的,就像 OP 已经说过的那样会很蹩脚。
  • 有时您会在异常块中看到 throw ex,这些异常块使用注入策略来确定应如何处理异常:catch( Exception ex ) { ExceptionStrategy( ex ); } ... void ExceptionStrategy( Exception ex ) { /* determine if you can handle/recover, else throw ex*/ } ... 但这种情况并不常见。
【解决方案2】:

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

throw new ArgumentException();

是有效的,但是

var ex = new ArgumentException();
throw ex;

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

【讨论】:

  • 我认为 OP 意味着在 重新抛出异常时是否应该使用throw ex;。
【解决方案3】:

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

这是一个可能的语言\编译器功能,尽管在实践中并没有增加它所具有的负面影响的任何价值。这与能够编写代码来访问空引用上的成员的情况类似——这当然是可能的,但没有价值,例如:

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

    ref1.ToString();

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

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

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

【讨论】:

  • 不确定我是否会将其与null 引用上的呼叫成员进行比较;这会给您一个运行时错误(此处故意避免使用“异常”一词,以避免混淆),而用于重新引发异常的 throw ex; 可以正常工作,但它可能会做的很可疑。
  • @Andrew 这个例子是为了说明语言\编译器可以让你表达有限或没有价值的东西。有一些语言\编译器特性可以让你做一些完全错误但由于其他原因而被遗漏或允许的事情,例如验证成本太高。
  • @chibacity - 我知道;但我会说空引用实际上比没有价值更糟糕;它实际上会使程序崩溃。
  • @Andrew 是的,我同意,但我想在这里画一个更大的图景:) 所以你认为重新启动堆栈跟踪在生产中是一件好事! “应用程序崩溃了,但是很好,我们有一个堆栈跟踪 - 哦,不,有些 t**t 有“throw ex”,我们没有堆栈跟踪 - grrr”。
  • 嗯...有趣;我不记得说这是件好事。但是通过throw ex; 重新抛出异常不会,本身导致程序崩溃;尝试在空引用上使用成员可以。更好的比较可能是与某些通常不明智的模式中的其他一些表面上“无害”的行为。
【解决方案4】:

throw ex 只是不知道或忘记throw; 的人所犯的错误或错字。

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

FxCop 规则CA2200RethrowToPreserveStackDetails 也是如此,通过使用throw; 正确邀请您到rethrow an exception:

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

【讨论】:

  • @fejesjoco:问题是:这个有什么实际用途吗?。我的答案在最后一段(注释之前)。
  • 我仍然不明白那些对问题的实际有效答案的反对意见。如果只是因为我的回答的 开头 没有回答问题,那么您可能应该在投票/否决之前阅读到最后。
  • 我同意你的观点 MainMa,我曾经遇到过这种情况,我必须重新抛出异常但也必须记录它..并且 throw 保留抛出异常的行号(即堆栈跟踪)而该行与 throw ex 没有变化...
  • 解释如何正确使用 throw 没有抓住问题的重点。
  • @chibacity:我想您没有阅读完整的答案。在所有情况下,我将答案的最后一句话移到了开头,所以现在必须更清楚了。
【解决方案5】:

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

反射

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

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

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

任务 API

在 .NET 任务 API 中,任务执行期间引发的异常被包装在 AggregateException 中。使用 async/await 时,框架将在幕后执行展开,以便抛出原始异常而不是包装器 AggregateException。

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

【讨论】:

    【解决方案6】:

    您可能想要记录异常,记录它,然后将其作为“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;
         }
    }
    

    【讨论】:

    • -1 throw; 是你应该在那里做的。再读一遍这个问题,因为你没有回答它。
    • 你在说什么?您知道OP问了什么以及我刚刚回答了什么吗?并给我 1 个使用 throw 的充分理由;在给定的示例中。我需要上层的异常详细信息吗? ..
    • 其实你是对的。你确实回答了这个问题。虽然我永远不会仅仅因为我记录了信息就隐藏它:)
    • 我认为在这种情况下我宁愿抛出一个不同的异常或者不记录它。例如。在设计图书馆时,我会在公共世界的边界捕获并记录,然后抛出 MyLibraryException。我只是没有看到这种场景在现实世界中的使用,但也许我没有看到大局
    • 我也可以想出throw ex; 的毫无价值的用法,但这仍然不能真正回答问题。当我回答您的评论时,我确实删除了我的反对票。但是我再次添加了它,因为您自己说过您永远不会像答案中那样做。因此没有真正回答这个问题。
    猜你喜欢
    • 2011-11-29
    • 2010-12-22
    • 2011-05-28
    • 1970-01-01
    • 1970-01-01
    • 2021-10-17
    • 2017-04-09
    • 2010-10-02
    • 2011-01-15
    相关资源
    最近更新 更多