为什么我们不抛出这些异常?


111

我遇到了这个MSDN页面,其中指出:

不要故意从您自己的源代码中抛出ExceptionSystemExceptionNullReferenceExceptionIndexOutOfRangeException

不幸的是,它不会费心解释原因。我可以猜出原因,但我希望有人对此问题更有权威。

前两个很明显,但是后两个看起来就像您想雇用的(事实上,我有)。

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


22
msdn.microsoft.com/zh-cn/library/ms182338.aspx中:如果在库或框架中抛出常规异常类型,例如Exception或SystemException,它将强制使用者捕获所有异常,包括他们执行的未知异常不知道该如何处理。
亨里克

3
为什么会引发NullReferenceException?
瑞克2014年

5
@Rik:NullArgumentException有些人可能会混淆两者。
回教徒本·达欧(Ben Dhaou)2014年

2
@Rik扩展方法,根据我的回答
Marc Gravell

3
您不应该丢掉的另一个是ApplicationException
Matthew Watson

Answers:


98

Exception是所有异常的基本类型,因此非常不确定。您永远不要抛出此异常,因为它根本不包含任何有用的信息。调用代码捕获异常不会使故意抛出的异常(从您的逻辑)与其他完全不希望的系统异常,并指出真正的错误。

相同的原因也适用于SystemException。如果查看派生类型的列表,则可以看到大量其他语义非常不同的异常。

NullReferenceException并且IndexOutOfRangeException是另一种。现在这些都是非常特殊的例外,因此抛出它们可能很好。但是,您仍然不希望抛出这些错误,因为它们通常意味着您的逻辑中存在一些实际错误。例如,空引用异常意味着您正在尝试访问的对象的成员null。如果您的代码有这种可能,那么您应该始终显式检查null并抛出一个更有用的异常(例如ArgumentNullException)。同样,IndexOutOfRangeException当您访问无效索引(在数组上,而不是列表上)时,s会发生。您应该始终确保首先不要这样做,并首先检查例如数组的边界。

还有其他一些例外,例如InvalidCastExceptionDivideByZeroException,这两个例外是由于代码中的特定错误而抛出的,通常意味着您做错了什么,或者您没有先检查某些无效值。通过从代码中有意识地抛出它们,您使调用代码更难确定是由于代码中的某些错误而抛出它们,还是仅仅因为您决定将它们重用于实现中。

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

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


3
第三部分对我来说毫无意义。当然,您应该避免引起这些错误,但是例如在编写IList实现时,它不能影响所请求的索引,当索引无效时,这是调用者的逻辑错误,并且您只能通知它们通过引发适当的异常来解决此逻辑错误。为什么IndexOutOfRangeException不合适呢?

7
@delnan如果要实现IList,那么你将被扔ArgumentOutOfRangeException接口文档建议。IndexOutOfRangeException是针对数组的,据我所知,您无法重新实现数组。

2
可能还有些相关的东西,NullReferenceException通常在内部作为AccessViolationException(IIRC的特殊情况)抛出,例如cmp [addr], addr,它尝试取消引用指针,并且如果它因访问冲突而失败,它将处理NRE和AVE之间的差异。在产生的中断处理程序中)。因此,除了语义原因外,还涉及一些作弊行为。null在无用的情况下,它也可能有助于阻止您手动检查-如果仍然要抛出NRE,为什么不让.NET这样做呢?
a安2014年

关于您的最后一个声明,关于自定义例外:我从未感到有必要这样做。也许我错过了一些东西。在什么情况下需要构造自定义异常类型来代替框架中的某些内容?
DonBoitnott 2014年

1
好吧,对于较小的应用程序,可能不需要它。但是,一旦您创建了一个更复杂的东西,其中各个零件都作为独立的“组件”工作,通常有必要针对自定义错误情况引入自定义例外。例如,如果您具有某个访问控制层,并且尽管未获得执行权限的权限而尝试执行某些服务,则可能会抛出自定义的“拒绝访问异常”之类的信息。或者,如果您有一个解析器来解析某些文件,则可能会有自己的解析器错误来报告给用户。

36

我怀疑最后两个的目的是防止与具有预期含义的内置异常混淆。但是,我认为,如果您保留例外的确切意图:它是的正确选择throw。例如,如果您正在编写一个自定义集合,则使用IndexOutOfRangeExceptionIMO 似乎比使用更加合理,更明确ArgumentOutOfRangeException。尽管List<T>可以选择后者,但BCL 中至少有 41个位置(由反射器提供)(不包括数组)可进行定制IndexOutOfRangeException-没有一个“低级别”足以值得特殊豁免。是的,我认为您可以公正地认为该准则很愚蠢。同样NullReferenceException 在扩展方法中很有用-如果您想保留以下语义:

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

抛出一个NullReferenceExceptionwhen objnull


2
“如果您保留异常的确切意图”-当然,如果确实是这种情况,那么无需首先进行测试就可以抛出异常?如果您已经对其进行了测试,那么它真的不是例外吗?
PugFugly 2014年

5
@PugFugly需要2秒钟的时间来查看扩展方法示例:不,无需测试就不会抛出该错误。如果SomeMethod()不需要执行成员访问,则强制执行它是不正确的。同样:在BCL中创建自定义的41个地方IndexOutOfRangeException和创建自定义的16个地方来说明这一点NullReferenceException
Marc Gravell

16
我认为扩展方法仍应抛出a ArgumentNullException而不是NullReferenceException。即使扩展方法中的语法糖允许使用与普通成员访问相同的语法,但它的工作原理仍大不相同。而获得NRE MyStaticHelpers.SomeMethod(obj)就是错的。

1
@PugFugly BCL是“基类库”,基本上是.NET中的核心内容。

1
@PugFugly:在许多情况下,未能抢先检测条件会导致在“不方便”的时间抛出异常。如果某个操作不能成功,那么尽早抛出异常比开始操作,中途执行然后必须清理产生的部分处理的混乱要好。
超级猫

5

如您所指出,在主题“ 抛出异常时应避免的事情下的“ 创建和抛出异常”(C#编程指南)中,Microsoft确实确实将异常类型列为不应从您自己的源代码中故意抛出的异常类型。System.IndexOutOfRangeException

相反,在本文(《 C#参考》)中,Microsoft似乎违反了自己的准则。这是Microsoft包含在其示例中的方法:

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

因此,Microsoft本身并不一致,因为它证明了IndexOutOfRangeException其文档中有关throw

这使我相信,至少对于的情况IndexOutOfRangeException,程序员有时可能会抛出该异常类型,并被认为是可以接受的做法。


1

当我阅读您的问题时,我问自己在什么条件下要抛出异常类型NullReferenceExceptionInvalidCastException或者ArgumentOutOfRangeException

我认为,当遇到这些异常类型之一时,我(开发人员)会感到担心,因为编译器正在与我对话。因此,允许您(开发人员)抛出此类异常类型等同于(编译器)出售责任。例如,这表明编译器现在应允许开发人员确定对象是否为null。但是做出这样的决定实际上应该是编译器的工作。

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


好点。但是,我认为说程序员应该让.NET Framework运行时抛出这些类型的异常(并且程序员应该以适当的方式处理它们)更为准确。
DavidRR 2014年

0

把讨论关于NullReferenceExceptionIndexOutOfBoundsException旁白:

那接球和投掷呢System.Exception?我在代码中抛出了很多此类异常,但是我从来没有被它搞砸。同样,我经常遇到非特定Exception类型,它对我也很好用。那为什么呢?

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

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

编辑:也从MSDN页面:

在普通执行过程中,不应使用异常来更改程序的流程。异常应仅用于报告和处理错误情况。

对于不同的异常类型,使用具有单独逻辑的catch子句也不是最佳实践。


异常消息仍然存在,以告知发生了什么。我无法想象,您可以为每个不同的错误创建新的异常类型,以防万一消费者可能希望以编程方式区分这些错误。
uebe

我之前的评论发生了什么?
DonBoitnott
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.