.NET中应为无效或意外参数抛出哪些异常?


163

.NET中应为无效或意外参数引发哪些类型的异常?我什么时候会选择一个而不是另一个?

跟进:

如果您有一个函数期望一个对应于一个月的整数并且传入了'42',那么您将使用哪个异常?即使这不是一个集合,它也会属于“超出范围”类别吗?

Answers:


249

我喜欢用:ArgumentExceptionArgumentNullException,和ArgumentOutOfRangeException

还有其他选择,它们没有过多地关注参数本身,而是整体上对调用进行了判断:

  • InvalidOperationException–该参数可能是确定的,但不是在对象的当前状态。归功于STW(以前是Yoooder)。还要投票给他答案
  • NotSupportedException–传入的参数是有效的,但此实现中不支持该参数。假设有一个FTP客户端,并且您通过了一个不支持该客户端的命令。

诀窍是抛出最能表达为什么无法按原样调用该方法的异常。理想情况下,应该详细说明发生错误的原因,错误原因以及解决方法。

当错误消息指向帮助,文档或其他资源时,我很喜欢。例如,Microsoft在其知识库文章中迈出了很好的第一步,例如“当我在Internet Explorer中访问网页时为什么收到“操作中止”错误消息?” 。遇到错误时,他们会将您指向错误消息中的知识库文章。他们做得不好的是他们没有告诉你,为什么会失败。

再次感谢STW(前Yoooder)的评论。


为了回应您的跟进,我将抛出ArgumentOutOfRangeException。查看MSDN关于此异常的说法:

ArgumentOutOfRangeException当调用方法并且传递给该方法的至少一个参数不是null引用(Nothing在Visual Basic中)且不包含有效值时,抛出。

因此,在这种情况下,您正在传递一个值,但这不是有效值,因为您的范围是1到12。但是,通过文档记录的方式可以清楚地知道API会抛出什么。因为尽管我可能会说ArgumentOutOfRangeException,但是另一个开发人员可能会说ArgumentException。简化并记录行为。


s!我同意您完全正确地回答了他的特定问题,但是请参阅下面的我的回答,以帮助完善防御性编码和验证参数;-D
STW

+1,但记录抛出什么异常以及为什么抛出异常比选择“正确的”异常更为重要。
pipTheGeek

@pipTheGeek-我认为这确实是一个有争议的问题。虽然文档确定无疑很重要,但它也希望使用该软件的开发人员能够积极主动或防御,并实际阅读文档的详细信息。我会选择友好/描述性的错误而不是好的文档,因为最终用户有机会看到其中一个而不是另一个;让最终用户向不良的程序员传达描述性错误的机会要比不良的程序员阅读完整文档的机会更大
STW

4
请注意,如果您捕获ArgumentException,它将也捕获ArgumentOutOfRange。
史蒂文·埃弗斯

怎么样FormatException:当参数的格式无效或复合格式的字符串格式不正确时引发的异常。
安东尼

44

我对Josh的答案投了赞成票,但想在清单中再加上一个:

如果参数有效,则应引发System.InvalidOperationException,但对象处于不应使用参数的状态。

来自MSDN的更新

如果因无效参数以外的原因导致调用方法失败,则使用InvalidOperationException。

假设您的对象具有PerformAction(enmSomeAction action)方法,有效的enmSomeActions是Open和Close。如果您连续两次调用PerformAction(enmSomeAction.Open),则第二次调用应引发InvalidOperationException(因为匹配有效,但不适用于控件的当前状态)

因为您已经通过防御性编程来做正确的事情,所以我要提的另一个例外是ObjectDisposedException。 如果您的对象实现IDisposable,那么您应该始终有一个跟踪已处置状态的类变量;如果您的对象已经被处置并且方法被调用,则应该引发ObjectDisposedException:

public void SomeMethod()
{
    If (m_Disposed) {
          throw new ObjectDisposedException("Object has been disposed")
     }
    // ... Normal execution code
}

更新:要回答您的后续问题:情况有点模棱两可,并且由于使用通用(不是.NET通用)意义上的通用数据类型来表示一组特定的数据而使情况变得更加复杂。枚举或其他强类型对象将是更理想的选择-但是我们并不总是拥有该控件。

我个人倾向于ArgumentOutOfRangeException并提供一条消息,指示有效值为1-12。我的理由是,当您谈论月份时,假设月份的所有整数表示形式都有效,那么您期望的值在1到12之间。如果仅某些月份(例如,具有31天的月份)有效,那么您将不会处理Range本身,而我将抛出一个表明有效值的通用ArgumentException,并且还将在方法的注释中记录它们。


1
好点子。这可以解释无效输入和意外输入之间的区别。+1
DanielBrückner09年

抱歉,我同意你的意思,只是不会偷走你的雷声。但自从您指出以来,我更新了我的答案
JoshBerke

38

根据实际值和最合适的例外情况:

如果不够精确,则只需从派生您自己的异常类ArgumentException

Yoooder的回答启发了我。如果输入在任何时候都无效,则该输入无效;而对于系统的当前状态,该输入无效,则该输入是意外的。因此,在后一种情况下,an InvalidOperationException是一个合理的选择。


6
摘自MSDN上InvalidOperationException的页面:“如果因非有效参数以外的原因导致调用方法失败,则使用InvalidOperationException。”
STW


3

ArgumentException

调用方法时,至少一个传递的参数不符合被调用方法的参数规范,则引发ArgumentException。ArgumentException的所有实例都应携带有意义的错误消息,描述无效的参数以及该参数的预期值范围。

对于特定类型的无效性,还存在一些子类。链接包含子类型的摘要以及何时应用子摘要。


1

简短答案:
都不

更长的答案:
使用Argument * Exception(在其上有产品的库(例如组件库)中除外)是一种气味。例外是处理特殊情况,而不是错误,也不是用户(即API使用者)的不足。

最长的答案:
除非编写库,否则将无效参数抛出异常是不礼貌的。
由于两个(或多个)原因,我更喜欢使用断言:

  • 断言不需要进行测试,而抛出断言则需要进行测试,并且针对ArgumentNullException进行测试看起来很荒谬(尝试一下)。
  • 断言可以更好地传达单元的预期用途,并且比类行为规范更接近于可执行文档。
  • 您可以更改断言违规的行为。例如,在调试编译中,有一个消息框就可以了,这样您的质量检查将立即受到打击(您的IDE也会在发生故障的地方中断),而在单元测试中,您可以将断言失败表示为测试失败。

这是处理null异常的样子(显然是讽刺的):

try {
    library.Method(null);
}
catch (ArgumentNullException e) {
    // retry with real argument this time
    library.Method(realArgument);
}

当预计情况会发生但异常时,应使用异常(发生超出用户控制范围的事情,例如IO故障)。Argument * Exception表示错误,应(通过我的看法)通过测试进行处理,并通过Debug.Assert进行辅助

顺便说一句:在这种情况下,您可能使用了Month类型,而不是int类型。C#在类型安全方面(Aspect#rulez!)不够完善,但有时您可以一起防止(或在编译时捕获)这些错误。

是的,MicroSoft在这方面是错误的。


6
恕我直言,当被调用的方法无法合理进行时,也应引发异常。这包括调用方传递假参数的情况。您会怎么做?返回-1?
约翰·桑德斯

1
如果无效的参数会导致内部函数失败,那么测试参数的有效性和从内部函数捕获InvalidArgumentException并将其包装为更多信息的优缺点是什么?在通常的情况下,后一种方法似乎可以提高性能,但是我看不到它做得太多。
超级猫

谷歌围绕这个问题进行的快速搜索表明,抛出一般异常是所有情况中最糟糕的做法。关于论据的断言,我认为这对小型个人项目有好处,但对企业应用程序却没有好处,在企业应用程序中,最有可能由于配置错误或对应用程序了解不足而导致无效论据。
最高

0

您可以使用一个标准的ArgumentException,也可以子类化并创建自己的类。有几个特定的​​ArgumentException类:

http://msdn.microsoft.com/zh-cn/library/system.argumentexception(VS.71).aspx

哪一个效果最好。


1
我不同意几乎所有情况。提供的.NET Argument * Exception类非常常用,它使您能够提供足够的特定信息以将问题通知使用者。
STW

需要澄清的是-我不同意几乎所有从Argument * Exception类派生的情况。使用.NET Argument Exceptions之一加上描述性和清晰的消息,可以为每种情况下参数无效的情况提供足够的详细信息。
STW

同意,但是我只是在描述可用的选项。我绝对应该对“首选”方法更加清楚。
Scott M.
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.