【问题标题】:Why are we not to throw these exceptions?为什么我们不抛出这些异常?
【发布时间】:2014-04-22 14:47:03
【问题描述】:

我遇到this MSDN page 说:

不要故意从您自己的源代码中抛出Exception、SystemException、NullReferenceException 或IndexOutOfRangeException。

不幸的是,它懒得解释原因。我可以猜到原因,但我希望在这个主题上更权威的人可以提供他们的见解。

前两个有一些明显的意义,但后两个似乎是你想要雇用的(事实上,我有)。

此外,这些是我们应该避免的唯一例外吗?如果还有其他的,它们是什么,为什么也应该避免它们?

【问题讨论】:

  • 来自msdn.microsoft.com/en-us/library/ms182338.aspx:如果你抛出一个通用的异常类型,例如库或框架中的Exception或SystemException,它会强制消费者捕获所有异常,包括他们不知道如何捕获的未知异常处理。
  • 为什么要抛出 NullReferenceException?
  • @Rik:类似于NullArgumentException,有些人可能会将两者混淆。
  • @Rik 扩展方法,根据我的回答
  • 另一个你不应该扔的是ApplicationException

标签: c# exception-handling


【解决方案1】:

抛开关于NullReferenceException 和IndexOutOfBoundsException 的讨论:

捕捉和投掷System.Exception。我在我的代码中经常抛出这种类型的异常,但我从来没有被它搞砸过。同样,我经常捕捉到不特定的Exception 类型,它对我来说也很有效。那么,这是为什么呢?

通常用户争辩说,他们应该能够区分错误原因。根据我的经验,只有极少数情况下您希望以不同的方式处理不同的异常类型。对于那些您希望用户以编程方式处理错误的情况,您应该抛出更具体的异常类型。对于其他情况,我不相信一般的最佳实践指南。

所以,关于投掷 Exception,我认为没有理由在所有情况下都禁止这样做。

编辑:也来自 MSDN 页面:

不应将异常用作普通执行的一部分来更改程序的流程。异常只能用于报告和处理错误情况。

针对不同异常类型的单独逻辑过度使用 catch 子句也不是最佳实践。

【讨论】:

  • 异常消息仍然存在,说明发生了什么。我无法想象你会为每个不同的错误创建一个新的异常类型,以防消费者可能想要以编程方式区分这些错误。
  • 我之前的评论怎么了?
【解决方案2】:

正如您所指出的,在主题 引发异常时要避免的事情 下的文章 Creating and Throwing Exceptions (C# Programmming Guide) 中,Microsoft 确实将 System.IndexOutOfRangeException 列为不应故意从您的自己的源代码。

然而,相比之下,在文章throw (C# Reference) 中,微软似乎违反了自己的指导方针。这是 Microsoft 在其示例中包含的一种方法:

static int GetNumber(int index)
{
    int[] nums = { 300, 600, 900 };
    if (index > nums.Length)
    {
        throw new IndexOutOfRangeException();
    }
    return nums[index];
}

因此,Microsoft 本身并不一致,因为它在 throw 的文档中演示了 IndexOutOfRangeException 的抛出!

这让我相信,至少对于 IndexOutOfRangeException 的情况,可能存在程序员抛出该异常类型 并被考虑的情况一种可接受的做法。

【讨论】:

    【解决方案3】:

    当我读到你的问题时,我问自己在什么情况下会抛出异常类型 NullReferenceException、InvalidCastException 或 ArgumentOutOfRangeException。

    在我看来,当遇到其中一种异常类型时,我(开发人员)会担心编译器正在与我交谈的警告。所以,允许你(开发者)抛出这样的异常类型就等于(编译器)出卖了责任。例如,这表明编译器现在应该允许开发人员决定一个对象是否为null。但是做出这样的决定应该是编译器的工作。

    PS:自 2003 年以来,我一直在开发自己的异常,因此我可以随心所欲地抛出它们。我认为这样做是一种最佳做法。

    【讨论】:

    • 好点。但是,我认为更准确的说法是程序员应该让 .NET Framework 运行时抛出这些类型的异常(并且程序员应该以适当的方式处理它们)。
    【解决方案4】:

    Exception 是所有异常的基本类型,因此非常不具体。你永远不应该抛出这个异常,因为它根本不包含任何有用的信息。调用异常代码捕获无法区分故意抛出的异常(来自您的逻辑)与其他完全不希望出现的系统异常并指出真正的错误。

    同样的原因也适用于SystemException。如果您查看派生类型的列表,您会发现大量其他具有非常不同语义的异常。

    NullReferenceException 和 IndexOutOfRangeException 是不同的类型。现在这些是非常具体的异常,所以抛出它们可能没问题。但是,您仍然不想抛出这些,因为它们通常意味着您的逻辑中存在一些实际错误。例如,空引用异常意味着您正在尝试访问对象的成员null。如果您的代码中有这种可能性,那么您应该始终显式检查 null 并抛出更有用的异常(例如 ArgumentNullException)。同样,IndexOutOfRangeExceptions 在您访问无效索引(在数组而非列表上)时发生。您应该始终确保您首先不这样做,并检查例如的边界。首先是一个数组。

    还有一些与这两个类似的其他异常,例如 InvalidCastException 或 DivideByZeroException,它们是针对您的代码中的特定错误而引发的,通常意味着您做错了什么或者您没有检查某些无效值第一的。通过故意从代码中抛出它们,您只会让调用代码更难确定它们是由于代码中的某些错误而被抛出,还是仅仅因为您决定在实现中重用它们。

    当然,这些规则也有一些例外(哈哈)。如果您正在构建可能导致与现有异常完全匹配的异常的东西,那么请随意使用它,尤其是当您尝试匹配某些内置行为时。只需确保您选择了一个非常具体的异常类型即可。

    一般来说,除非您找到满足您需求的(特定)异常,否则您应该始终考虑为特定的预期异常创建自己的异常类型。尤其是在编写库代码时,这对于分离异常源非常有用。

    【讨论】:

    • 第三部分对我来说意义不大。当然,您应该避免导致这些错误开始,但是当您例如编写一个IList 实现,你无法影响请求的索引,这是调用者的 索引无效时的逻辑错误,你只能通过抛出一个通知他们这个逻辑错误适当的例外。为什么IndexOutOfRangeException 不合适?
    • @delnan 如果您正在实施IList,那么您将按照interface documentation 的建议抛出ArgumentOutOfRangeException。 IndexOutOfRangeException 用于数组,据我所知,您无法重新实现数组。
    • 什么也可能有点相关,NullReferenceException 通常在内部作为AccessViolationException 的特例被抛出(IIRC 测试类似于cmp [addr], addr,即它试图取消引用指针如果它因访问冲突而失败,它会在生成的中断处理程序中处理 NRE 和 AVE 之间的差异)。所以除了语义上的原因外,还涉及到一些作弊。它还可能有助于阻止您在没有帮助时手动检查 null - 如果您仍然要抛出 NRE,为什么不让 .NET 来做呢?
    • 关于你最后的声明,关于自定义异常:我从来没有觉得有必要这样做。也许我错过了一些东西。在什么情况下需要制作自定义异常类型来代替框架中的某些内容?
    • 嗯,对于较小的应用程序可能不需要它。但是,一旦您创建了更复杂的东西,其中各个部分作为独立的“组件”工作,那么为自定义错误情况引入自定义异常通常是有意义的。例如,如果你有一些访问控制层,并且你尝试执行一些服务,尽管你没有被授权这样做,你可能会抛出一个自定义的“访问被拒绝异常”或其他东西。或者,如果您有一个解析某个文件的解析器,您可能有自己的解析器错误要报告给用户。
    【解决方案5】:

    我怀疑最后 2 个的目的是防止与具有预期含义的内置异常混淆。但是,我认为如果您要保留异常的确切意图:它是throw 的正确意图。例如,如果您正在编写自定义集合,使用IndexOutOfRangeException 似乎完全合理——IMO 比ArgumentOutOfRangeException 更清晰、更具体。虽然 List<T> 可能会选择后者,但在 BCL(不包括阵列)中有 至少 41 个位置(由反射器提供)抛出定制的 IndexOutOfRangeException - 其中没有一个是“低级别“足以值得特别豁免。所以,是的,我认为您可以公正地辩称该准则很愚蠢。同样,NullReferenceException 在扩展方法中有点用——如果你想保留以下语义:

    obj.SomeMethod(); // this is actually an extension method
    

    当obj 是null 时抛出NullReferenceException。

    【讨论】:

    • “如果您保留异常的确切意图” - 如果是这种情况,肯定会抛出异常而无需您首先对其进行测试?而且,如果您已经对其进行了测试,那么它并不是一个例外?
    • @PugFugly 花 2 秒时间看一下扩展方法示例:不,如果您不必测试它,它不会被抛出。如果SomeMethod() 不需要进行成员访问,那么强制它是不正确的。同样:在 BCL 中创建自定义 IndexOutOfRangeException 的 41 个位置和创建自定义 NullReferenceException 的 16 个位置考虑这一点
    • 我认为扩展方法仍然应该抛出 ArgumentNullException 而不是 NullReferenceException。即使扩展方法中的语法糖允许与普通成员访问相同的语法,它的工作方式仍然非常不同。从MyStaticHelpers.SomeMethod(obj) 获取 NRE 是错误的。
    • @PugFugly BCL 是“基类库”,基本上是 .NET 中的核心内容。
    • @PugFugly:在许多情况下,如果未能先发制人地检测到某个条件,则会导致在“不方便”的时间抛出异常。如果一个操作不会成功,尽早抛出异常比开始操作,进行到一半,然后必须清理产生的部分处理的混乱要好。
    猜你喜欢
    • 2015-12-22
    • 2014-07-09
    • 2017-07-03
    • 1970-01-01
    • 2021-04-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多