【问题标题】:Best exception for an invalid generic type argument无效泛型类型参数的最佳例外
【发布时间】:2009-09-11 18:35:03
【问题描述】:

我目前正在为 UnconstrainedMelody 编写一些代码,其中包含与枚举有关的通用方法。

现在,我有一个带有一堆方法的静态类,这些方法用于“标志”枚举。我不能将其添加为约束...因此它们也可能会与其他枚举类型一起调用。在这种情况下,我想抛出一个异常,但我不确定要抛出哪一个。

只是为了具体化,如果我有这样的事情:

// Returns a value with all bits set by any values
public static T GetBitMask<T>() where T : struct, IEnumConstraint
{
    if (!IsFlags<T>()) // This method doesn't throw
    {
        throw new ???
    }
    // Normal work here
}

什么是最好的抛出异常? ArgumentException 听起来合乎逻辑,但它是一个 type 参数而不是普通参数,这很容易混淆。我应该介绍我自己的TypeArgumentException 班级吗?使用InvalidOperationException? NotSupportedException?还有什么?

宁愿不为此创建自己的例外,除非这显然是正确的做法。

【问题讨论】:

  • 我今天在编写一个泛型方法时偶然发现了这一点,其中对所使用的类型提出了额外的要求,而这些要求无法用约束来描述。我很惊讶没有在 BCL 中找到异常类型。但是这个确切的困境是我几天前在同一个项目中也面临的一个问题,即一个只能使用 Flags 属性的泛型。诡异!

标签: c# generics exception


【解决方案1】:

NotSupportedException 听起来 很适合,但文档明确指出它应该用于不同的目的。来自MSDN课上的备注:

有些方法不是 在基类中支持,与 期望这些方法将 在派生类中实现 反而。派生类可能 仅实现方法的子集 从基类,并抛出 NotSupportedException 的 不受支持的方法。

当然,NotSupportedException 有一种方法显然已经足够好了,尤其是考虑到它的常识意义。话虽如此,我不确定它是否恰到好处。

鉴于Unconstrained Melody的目的...

使用泛型可以做很多有用的事情 类型约束为 "T : enum" 或 "T : 委托” - 但不幸的是,这些在 C# 中是被禁止的。

这个实用程序库可以解决使用 ildasm/ilasm 的禁令...

...尽管在创建自定义Exceptions 之前我们必须满足很高的举证责任,但似乎新的Exception 可能是有序的。像InvalidTypeParameterException 这样的东西可能在整个库中都有用(或者可能没有——这肯定是一个边缘案例,对吧?)。

客户是否需要能够将此与 BCL 异常区分开来?客户什么时候可能会不小心使用香草enum 调用它?您将如何回答What factors should be taken into consideration when writing a custom exception class?接受的答案提出的问题

【讨论】:

  • 事实上,首先抛出一个仅限内部的异常几乎很诱人,就像代码合同所做的那样......我不相信任何人应该抓住它。
  • 太糟糕了,它不能只返回 null!
  • 我将使用 TypeArgumentException。
  • 向框架添加异常可能有很高的“举证负担”,但定义自定义异常不应该。 InvalidOperationException 之类的东西很恶心,因为“Foo 要求 Collection Bar 添加一些已经存在的东西,所以 Bar 抛出 IOE”和“Foo 要求 collection Bar 添加一些东西,所以 Bar 调用 Boz 会抛出 IOE,尽管 Bar 并不期待it to" 都会抛出相同的异常类型;期望捕获第一个的代码不会期望后者。说了这么多……
  • ...我认为这里支持框架异常的论点比自定义异常更有说服力。 NSE 的一般性质是,当一个对象作为一般类型的引用,以及引用所指向的对象的某些但不是全部的特定类型将支持一种能力时,试图在不支持的特定类型上使用该能力'不支持它应该抛出NSE。我认为Foo&lt;T&gt; 是“通用类型”,Foo&lt;Bar&gt; 在这种情况下是“特定类型”,即使它们之间没有“继承”关系。
【解决方案2】:

我会避免 NotSupportedException。此异常用于未实现方法且有属性指示不支持此类操作的框架中。这里不适合

我认为 InvalidOperationException 是您可以在这里抛出的最合适的异常。

【讨论】:

  • 感谢您对 NSE 的提醒。顺便说一句,也欢迎您的同事提供意见...
  • 关键是,Jon 需要的功能在 BCL 中没有任何类似之处。编译器应该捕获它。如果您从 NotSupportedException 中删除“属性”要求,那么您提到的内容(例如 ReadOnly 集合)与 Jon 的问题最接近。
  • 一点 - 我确实有一个 IsFlags 方法(它必须是一个通用的方法),它 sort 表示不支持这种类型的操作。 . 所以从这个意义上说,NSE 是合适的。即调用者可以先检查。
  • @Jon:我认为即使您没有这样的属性但是您类型的所有成员都固有地依赖于 Tenum 的事实用Flags装饰,抛出NSE是有效的。
  • @Jon: StupidClrException 是个有趣的名字 ;)
【解决方案3】:

泛型编程不应在运行时抛出无效类型参数。它不应该编译,你应该有一个编译时强制执行。我不知道IsFlag&lt;T&gt;() 包含什么,但也许您可以将其转换为编译时强制执行,例如尝试创建一种只能使用“标志”创建的类型。也许traits 课程可以提供帮助。

更新

如果你必须抛出,我会投票给 InvalidOperationException。原因是泛型类型具有参数,并且与(方法)参数相关的错误以 ArgumentException 层次结构为中心。但是,ArgumentException 上的 recommendation 声明

如果失败不涉及 争论自己,然后 InvalidOperationException 应该是 用过。

其中至少有一个信念飞跃,即 method 参数建议也将应用于 generic 参数,但没有比这更好的了SystemException 层次结构恕我直言。

【讨论】:

  • 不,这不可能在编译时受到限制。 IsFlag&lt;T&gt; 确定枚举是否应用了[FlagsAttribute],CLR 没有基于属性的约束。它会在一个完美的世界中 - 或者会有其他方式来限制它 - 但在这种情况下它只是不起作用:(
  • (虽然一般原则 +1 - 我希望能够能够约束它。)
【解决方案4】:

我会使用 NotSupportedException,因为这就是您所说的。 不支持除特定枚举之外的其他枚举。这当然会在异常消息中更清楚地说明。

【讨论】:

【解决方案5】:

我会选择NotSupportedException。虽然ArgumentException 看起来不错,但当传递给方法的参数不可接受时,它确实是意料之中的。类型参数是您要调用的实际方法的定义特征,而不是真正的“参数”。 InvalidOperationException 应该在您执行的操作在某些情况下有效但在特定情况下不可接受时抛出。

NotSupportedException 在操作本身不受支持时抛出。例如,当实现一个特定成员对类没有意义的接口时。这看起来像一个类似的情况。

【讨论】:

  • 嗯。它仍然完全感觉不对,但我认为它会是最接近它的东西。
  • Jon:感觉不对,因为我们自然希望它会被编译器捕获。
  • 是的。这是一种奇怪的约束,我想应用但不能:)
【解决方案6】:

显然,Microsoft 为此使用了ArgumentException,如例外部分中的Expression.Lambda<>Enum.TryParse<>Marshal.GetDelegateForFunctionPointer<> 示例所示。我也找不到任何其他说明的示例(尽管搜索了 TDelegateTEnum 的本地参考源)。

因此,我认为可以安全地假设,至少在 Microsoft 代码中,将ArgumentException 用于除基本变量之外的无效泛型类型参数是一种常见做法。鉴于docs 中的异常描述并没有区分它们,所以也不算太夸张。

希望它能一劳永逸地解决问题。

【讨论】:

  • 框架中的单个示例对我来说是不够的,不 - 考虑到我认为 MS 在其他情况下做出错误选择的地方的数量:) 我不会从 TypeArgumentException ArgumentException,仅仅因为类型参数不是常规参数。
  • 就“这是 MS 一贯的做法”而言,这肯定更引人注目。它在匹配文档方面并没有使其更具吸引力......而且我知道 C# 团队中有很多人非常关心常规参数和类型参数之间的区别:) 但是感谢您提供的示例 -他们很有帮助。
  • @Jon Skeet:进行了编辑;现在它包括来自不同 MS 库的 3 个示例,所有示例都将 ArgumentException 记录为抛出的一个;因此,如果这是一个糟糕的选择,至少它是一个始终如一的糟糕选择。 ;) 我猜微软假设常规参数和类型参数都是参数;我个人认为这样的假设是相当合理的。 ^^'
  • 啊,没关系,看来你已经注意到了。很高兴我能帮助你。 ^^
  • 我认为我们必须同意不同意对他们一视同仁是否合理。在反射或语言规则等方面,它们当然不一样......它们的处理方式非常不同。
【解决方案7】:

我选择 NotSupportedExpcetion。

【讨论】:

    【解决方案8】:

    在有问题的任何情况下都应始终抛出自定义异常。无论 API 用户需要什么,自定义异常都将始终有效。如果开发人员不在乎,他可以捕获任一异常类型,但如果开发人员需要特殊处理,他将是 SOL。

    【讨论】:

    • 此外,开发人员应该记录在 XML cmets 中抛出的所有异常。
    【解决方案9】:

    我总是对编写自定义异常保持警惕,纯粹是因为它们并不总是清楚地记录下来,如果命名不正确会引起混乱。

    在这种情况下,我会为标志检查失败抛出一个 ArgumentException。这完全取决于偏好。我见过的一些编码标准甚至定义了在这种情况下应该抛出哪些类型的异常。

    如果用户试图传入不是枚举的东西,那么我会抛出 InvalidOperationException。

    编辑:

    其他人提出了一个有趣的观点,即不支持这一点。我对 NotSupportedException 唯一关心的是,通常这些是当“暗物质”被引入系统时抛出的异常,或者换句话说,“这个方法必须在这个接口上进入系统,但我们赢了直到 2.4 版才打开它”

    我还看到 NotSupportedExceptions 作为许可异常抛出“您正在运行此软件的免费版本,不支持此功能”。

    编辑 2:

    另一个可能的:

    System.ComponentModel.InvalidEnumArgumentException  
    

    使用作为枚举数的无效参数时引发的异常。

    【讨论】:

    • 我将它限制为一个枚举(经过一些诡计多端的操作)——这只是我担心的标志。
    • 我认为那些获得许可的人应该抛出一个继承自 InvalidOperationExceptionLicensingException 类的实例。
    • 我同意 Mehrdad,不幸的是,例外是框架中有很多灰色的领域之一。但我确信这对于很多语言都是一样的。 (不是说我会回到 vb6 的运行时错误 13 呵呵)
    【解决方案10】:

    我也会投票给 InvalidOperationException。如果有人感兴趣,我不久前根据Framework Design Guidelines 2nd Ed..NET exception throwing guidelines 上做了一个(不完整的)流程图。

    【讨论】:

      【解决方案11】:

      如何从 NotSupportedException 继承。虽然我同意@Mehrdad 的观点,这是最有意义的,但我听到你的观点,它似乎并不完美。因此,从 NotSupportedException 继承,这样针对您的 API 进行编码的人仍然可以捕获 NotSupportedException。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-01-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-11-07
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多