【问题标题】:What exceptions should be thrown for invalid or unexpected parameters in .NET?.NET 中的无效或意外参数应引发哪些异常?
【发布时间】:2010-10-20 22:05:38
【问题描述】:

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

跟进:

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

【问题讨论】:

  • 虽然我同意大多数给出的答案(因为它们似乎足够合理),microsoft clearly states 在这种特定情况下应该是ArgumentOutOfRangeException。如果链接发生变化,这里是相关部分:ArgumentOutOfRangeException when the value of an argument is outside the range of acceptable values; for example, when the value "46" is passed as the month argument during the creation of a DateTime

标签: c# .net vb.net exception


【解决方案1】:

argument exception.

  • System.ArgumentException
  • System.ArgumentNullException
  • System.ArgumentOutOfRangeException

【讨论】:

    【解决方案2】:

    取决于实际值和最适合的异常:

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

    Yooder 的回答启发了我。如果输入在任何时候都无效,则输入是invalid,如果输入对于系统的当前状态无效,则输入是unexpected。所以在后一种情况下,InvalidOperationException 是一个合理的选择。

    【讨论】:

    • 取自 MSDN 关于 InvalidOperationException 的页面:“InvalidOperationException 用于由于无效参数以外的原因导致方法调用失败的情况。”
    【解决方案3】:

    我喜欢使用:ArgumentExceptionArgumentNullExceptionArgumentOutOfRangeException

    还有其他选项,它们不会过多地关注参数本身,而是将调用作为一个整体来判断:

    • InvalidOperationException – 参数可能没问题,但不是对象的当前状态。 归功于 STW(以前的 Yoooder)。也投票给his answer
    • NotSupportedException – 传入的参数是有效的,但在此实现中不支持。想象一个 FTP 客户端,你传递了一个客户端不支持的命令。

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

    我喜欢错误消息指向帮助、文档或其他资源。例如,微软在他们的知识库文章中迈出了良好的第一步,例如“Why do I receive an "Operation aborted" error message when I visit a Web page in Internet Explorer?”。当您遇到错误时,他们会将您指向错误消息中的知识库文章。他们做得不好的是他们没有告诉你,具体为什么失败。

    再次感谢 STW(前 Yoooder)的 cmets。


    作为对您的跟进的回应,我会抛出一个ArgumentOutOfRangeException。看看 MSDN 关于这个异常的说法:

    ArgumentOutOfRangeException 被抛出 当一个方法被调用并且至少 传递给的参数之一 方法不为空 参考(Nothing 在 Visual Basic 中) 并且不包含有效值。

    因此,在这种情况下,您传递了一个值,但这不是一个有效值,因为您的范围是 1-12。但是,您记录它的方式清楚地表明您的 API 抛出了什么。因为虽然我可能会说ArgumentOutOfRangeException,但另一个开发人员可能会说ArgumentException。简化并记录行为。

    【讨论】:

    • 嘘!我同意您完全正确地回答了他的具体问题,但请参阅下面的回答以帮助完善防御性编码和验证参数;-D
    • +1 但记录抛出了什么异常以及为什么比选择“正确”的异常更重要。
    • @pipTheGeek - 我认为这确实是一个有争议的问题。虽然文档化是绝对重要的,但它也希望消费开发人员能够主动或防御,并实际详细阅读文档。我会选择友好/描述性错误而不是好的文档,因为最终用户有机会看到其中一个而不是另一个;与阅读完整文档的糟糕程序员相比,让最终用户向糟糕的程序员传达描述性错误的机会更大
    • 注意,如果你捕获了 ArgumentException,它也会捕获 ArgumentOutOfRange。
    • FormatException 怎么样:当参数的格式无效或复合格式字符串格式不正确时抛出的异常。
    【解决方案4】:

    ArgumentException:

    ArgumentException 抛出时 方法被调用并且至少其中之一 传递的参数不符合 被调用的参数说明 方法。所有实例 ArgumentException 应该带有一个 有意义的错误信息描述 无效的论点,以及 的预期值范围 论据。

    对于特定类型的无效性也存在一些子类。该链接包含子类型的摘要以及应适用的时间。

    【讨论】:

      【解决方案5】:

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

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

      哪个效果最好。

      【讨论】:

      • 我几乎不同意所有情况;所提供的 .NET Argument*Exception 类非常常用,并且使您能够提供足够的特定信息来通知消费者该问题。
      • 澄清一下——我不同意几乎所有从 Argument*Exception 类派生的情况。使用 .NET 参数异常之一加上描述性和清晰的消息可以或多或少地为参数无效的每种情况提供足够的详细信息。
      • 同意,但我只是在描述可用的选项。我绝对应该更清楚“首选”方法。
      【解决方案6】:

      我投票给Josh's answer,但想在列表中再添加一个:

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

      更新取自 MSDN:

      InvalidOperationException 用于 未能调用 方法是由其他原因引起的 无效参数。

      假设您的对象有一个 PerformAction(enmSomeAction action) 方法,有效的 enmSomeAction 是 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,并且我还会在方法的 cmets 中记录它们。

      【讨论】:

      • 好点。这可能解释了无效输入和意外输入之间的区别。 +1
      • Psst,我同意你的观点,只是不会偷走你的风头。但是既然你指出了,我更新了我的答案
      【解决方案7】:

      简答:
      都没有

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

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

      • 断言不需要测试, 而 throw 断言做,并测试 反对 ArgumentNullException 看起来 荒谬(试试看)。
      • 断言更好地传达了单元的预期用途,并且是 更接近可执行 文档而不是类行为 规格。
      • 您可以更改断言违规的行为。例如,在调试编译中,一个消息框很好,这样你的 QA 会立即用它来打击你(你也会让你的 IDE 在它发生的地方中断),而在单元测试中你可以将断言失败指示为测试失败.

      以下是空异常的处理方式(显然是讽刺):

      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!),但有时您可以一起防止(或在编译时捕获)这些错误。

      是的,微软在这方面是错误的。

      【讨论】:

      • 恕我直言,当被调用的方法无法合理进行时,也应该抛出异常。这包括调用者传递虚假参数的情况。你会怎么做?返回 -1?
      • 如果无效参数会导致内部函数失败,那么测试参数的有效性与从内部函数中捕获 InvalidArgumentException 并用更丰富的信息包装起来的优缺点是什么?后一种方法在常见情况下似乎可以提高性能,但我没有看到它做得太多。
      • 围绕这个问题进行的快速谷歌搜索表明抛出一般异常是最糟糕的做法。关于论点的断言,我认为这在小型个人项目上是有好处的,但在企业应用程序上却没有,因为无效的论点很可能是由于配置错误或对应用程序的理解不足。
      • 你到底是如何发明这个论点的真正价值的?只需将 ArgumentNullException(或包装它的一些异常)传播到最高点并在那里处理它(向用户显示一些消息,记录它,将适当的退出代码返回到控制台,等等)。当函数无法履行其契约时,将使用异常,通常除了中止操作并向用户指示失败之外,没有其他方法可以处理它们。
      • 永远不应捕获 ArgumentNullException - 因为它基本上表示调用代码中存在错误。但是,当您在远离原始调用的某个地方有一个明确的 ArgumentNullException 而不是 NullReferenceException 时,发现该错误会变得容易得多。
      猜你喜欢
      • 2012-02-20
      • 1970-01-01
      • 1970-01-01
      • 2019-12-07
      • 2013-09-16
      • 1970-01-01
      • 2012-08-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多