【问题标题】:Why does C# have a separate rethrow statement (throw;) instead of overloading throw ex?为什么 C# 有一个单独的 rethrow 语句 (throw;) 而不是重载 throw ex?
【发布时间】:2014-06-16 18:03:29
【问题描述】:

当抛出异常时,保留堆栈跟踪是最常见的期望行为,在 Java 中这可以通过 throw ex; 获得,但在 C# 中必须使用 throw;。 (还请注意,许多 C# 程序员经常会错误地使用 throw ex; 而不是 throw;)。

如果有时必须清除堆栈跟踪(这种情况不太常见),则可以只抛出一个新异常,例如 throw new MyException(ex.Message, otherDetails);

那么,考虑到上述情况,在 C# 中使用单独的 throw; 语句有什么好处?

或者,换句话说:为什么 C# 使用一个特殊的单独语句 (throw;),这是鲜为人知的,用于最常用的情况(当用户想要保留堆栈跟踪时),它使用更自然的throw ex; 对于不太常见的情况(清除堆栈跟踪时)?还有其他我没有介绍的可能用例吗?

C# 和 Java 中的代码示例:

// This is the C# design
try
{
//...
}
catch (MyException ex)
{
   // rethrow so that stack trace is preserved
   ex.AppendData(contextVariable);
   throw;
}
catch (PrivateException ex)
{
   // throw new exception so stack trace is not preserved
   throw new PublicException(ex.Message);
}


// This is the Java design
try
{
//...
}
catch (MyException ex)
{
   // rethrow so that stack trace is preserved
   ex.AppendData(contextVariable);
   throw ex;
   // and I can even choose to use something like, where ProcessException(ex) will return the same ex
   throw ProcessException(ex, contextVariable);
}
catch (PrivateException ex)
{
   // throw new so stack trace is not preserved
   throw new PublicException(ex.getMessage());
}

【问题讨论】:

  • 询问自 C# 版本 1 以来一直存在的语言设计功能似乎很奇怪。
  • @JohnSaunders 虽然该功能是 C# 版本 1 的形式,但我仍然不明白其背后的原因。
  • 请查看stackoverflow.com/questions/730250/…(我相信这完全涵盖了您的问题的一部分,不是基于非观点或历史研究的部分)。随时提出新问题,明确说明您在理解该行为时遇到的问题。
  • 对于历史研究,请确保在blogs.msdn.com 上找到并阅读相应的博客并将发现添加到问题中。请注意,历史研究问题通常过于宽泛,因此请务必说明您需要留下的充分理由。
  • @AlexeiLevenkov 这个问题不是重复的。另一个问题问有什么区别,但我问为什么需要两种不同的陈述。如果这是基于意见的,我应该在哪里问?我仍然在 stackoverflow 上找到许多其他基于意见的问题,这些问题尚未结束,我认为基于意见的答案对于更好地理解不同主题很有价值。

标签: c# syntax exception-handling


【解决方案1】:

两者都抛出变体的原因

catch (SomeExceptionType ex)
{
    frobnicate(ex);
    throw;
    throw ex;
}

存在是因为每个都有用例。只有第一个对调试发生异常的代码有用,但在成熟的库中,几乎可以肯定原因是库外部的(硬件/操作系统环境中的故障​​,或者用户传递的参数错误)图书馆)。在这种情况下,库内部的细节对使用库的程序员没有帮助,实际上可能会引起更多的混乱。

此外,闭源库,尤其是在 SaaS 模型中远程访问的库1(网络服务)可能不希望公开实施细节,因为这些可能包含专有知识产权。在这种情况下,throw ex; 更可取。

您说 Java throw ex; 保留了现有的堆栈跟踪。这样的语言如何避免信息泄露?必须将除堆栈跟踪之外的所有内容都复制到一个新的异常对象,这在引发更多派生类型的异常时尤其困难。 C# 的选择使得这两种行为都非常容易获得。

(您可能仍希望执行过滤的深层复制,以防信息在其他异常属性中泄露,例如InnerException。但如果您愿意,C# 允许您跳过复制步骤。)

1 可以通过其他方式发现本地库的实现细节。保护这些细节requires a webservice-like execution model,保持对代码的完全控制。

【讨论】:

  • 你为什么要throw 而不是仅仅允许原始异常进入调用堆栈?
  • @RobertHarvey:用于记录目的?
  • 我认为当您捕获一个或多个特定异常以处理并抛出任何其他异常时,“抛出”很有用。
  • @RobertHarvey:也许你会有条件地这样做,而且条件不仅仅涉及类型信息。也许你在重新抛出之前在处理程序中做其他事情。代码 sn-p 旨在显示上下文,而不是实际可用的代码段(无论如何第二次抛出都无法访问)
  • @RobertHarvey 我想捕获一个异常,向它添加一些上下文信息(此时很容易获得),然后让它进入调用堆栈。
【解决方案2】:

throw expression; 语句的语义在 C# 和 Java 中是不同的。在 Java 中,异常堆栈跟踪是在实例化异常对象时捕获的。在 C# 中,异常堆栈跟踪是在抛出异常对象时捕获的。

此外,CLI (ECMA-335) 提供了一个特殊的字节码指令 rethrow,它与 throw 在两个主要方面不同:

  1. rethrow 抛出的异常与当前正在处理的异常实例相同。
  2. rethrow 指令保留原始堆栈跟踪。

这些语义差异要求 C# 提供两种不同的引发异常的形式,或者取消 C# 用户能够在预期的重新引发操作期间保留原始异常堆栈跟踪的能力(不要与 rethrow 混淆)字节码指令,可通过throw; 语法专门访问)。

【讨论】:

  • 或者去掉不保留原始堆栈跟踪的选项。
  • @BenVoigt 这需要编写 C# 规范,使 throw expression; 的行为取决于 both expression 的形式以及语句是否出现在里面catch 块与否。如果你有catch (Exception e) { var e2 = e; throw e2; },会发生什么?
  • 并非如此。只是if (the specified Exception object has never been thrown) capture new stack trace; else rethrow; 它也有助于类似任务的异常传播。
  • 明确地说,我喜欢现有的设计。但我不喜欢错误的二分法。
  • @BenVoigt 看起来就这么简单,但它已经被扔到另一个线程或域中作为传递回当前线程或域。
猜你喜欢
  • 2020-12-04
  • 2010-12-22
  • 1970-01-01
  • 2013-01-18
  • 2010-11-04
  • 1970-01-01
  • 1970-01-01
  • 2018-02-19
  • 2018-02-27
相关资源
最近更新 更多