无效的泛型类型参数的最佳例外


106

我目前正在为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听起来很合逻辑,但这是类型参数而不是普通参数,这很容易使事情混淆。我应该介绍自己的TypeArgumentException课程吗?使用InvalidOperationExceptionNotSupportedException?还要别的吗?

宁愿没有为此创造我自己的异常,除非它显然是做正确的事。


我今天在编写通用方法时偶然发现了这一点,该方法对使用的类型提出了额外的要求,这些要求无法用约束来描述。我很惊讶没有在BCL中找到异常类型。但是,几天前,我在同一个项目中遇到了一个确切的难题,该难题仅适用于Flags属性。幽灵般的!
安德拉斯·佐坦

Answers:


46

NotSupportedException 听起来似乎很合适,但是文档明确指出应将其用于其他目的。来自MSDN类的评论:

有一些基类不支持的方法,希望这些方法将在派生类中实现。派生类可能仅实现基类中方法的子集,并为不受支持的方法抛出NotSupportedException。

当然,有一种方法NotSupportedException显然足够好,特别是考虑到其常识性的意义。话虽如此,我不确定这是否正确。

鉴于无限制旋律的目的...

泛型方法/类可以完成各种有用的操作,其中类型约束为“ T:枚举”或“ T:委托”-但不幸的是,这些在C#中被禁止。

该实用程序库可解决使用ildasm / ilasm的禁忌...

... Exception尽管建立习俗之前我们不得不面对大量的举证责任,但似乎新的秩序还是有序的Exceptions。诸如此类的东西InvalidTypeParameterException可能在整个库中都是有用的(或者可能不是,这肯定是一个极端的情况,对吗?)。

客户是否需要能够将其与BCL例外区分开来?客户什么时候会不小心使用香草enum呢?您将如何回答接受的答案提出的问题,即编写自定义异常类时应考虑哪些因素?


实际上,首先要抛出一个内部唯一的异常几乎是诱人的,就像代码合同一样……我不相信任何人都应该抓住它。
乔恩·斯基特

太糟糕了,它不能只返回null!
Jeff Sternal

25
我要使用TypeArgumentException。
乔恩·斯基特

向框架添加异常可能会带来很高的“举证负担”,但定义自定义异常则不会。这样的事情InvalidOperationException很棘手,因为“ Foo要求集合Bar添加已经存在的东西,所以Bar抛出IOE”和“ Foo要求集合Bar添加东西,所以Bar呼叫Boz,即使Bar并不期望它也会抛出IOE”都将抛出相同的异常类型;期望抓住第一个的代码不会期望后者。话虽如此……
超级猫

...我认为这里支持框架异常的论点比自定义异常更具说服力。NSE的一般性质是,当将对象引用为通用类型,并且引用点将支持该功能的对象的某些但不是全部特定类型时,尝试在不支持该功能的特定类型上使用该功能不支持它应该抛出NSE。我会认为a Foo<T>是“通用类型”,并且Foo<Bar>在这种情况下是“特定类型”,即使它们之间没有“继承”关系。
超级猫

24

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

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


感谢您对NSE的注意。也会欢迎您的同事的意见,顺便说一句...
Jon Skeet

关键是,乔恩需要的功能在BCL中没有类似之处。编译器应该捕获它。如果从NotSupportedException中删除“属性”要求,则您提到的内容(如ReadOnly集合)与Jon的问题最接近。
Mehrdad Afshari

有一点-我有一个IsFlags方法(它必须是通用的方法),这是排序表明这种类型的操作是不支持的...所以在这个意义上NSE将是适当的。即呼叫者可以先检查。
乔恩·斯基特

@Jon:我认为即使您没有这样的属性,您类型的所有成员本质上都依赖于Tenum修饰的事实Flags,所以抛出NSE是有效的。
Mehrdad Afshari

1
@Jon:取StupidClrException个有趣的名字;)
Mehrdad Afshari 2009年

13

泛型编程不应在运行时抛出无效的类型参数。它不应该编译,您应该具有编译时间强制。我不知道IsFlag<T>()其中包含什么,但是也许您可以将其转换为编译时强制执行,例如尝试创建只能使用“标志”创建的类型。也许一traits堂课会有所帮助。

更新资料

如果必须抛出,我将投票赞成InvalidOperationException。原因是泛型类型具有参数,并且与(方法)参数相关的错误以ArgumentException层次结构为中心。但是,关于ArgumentException 的建议指出:

如果失败不涉及参数本身,则应使用InvalidOperationException。

至少有一个信念上的飞跃,方法参数建议也将应用于通用参数,但是SystemException hierachy imho并没有更好的选择。


1
不,没有办法可以在编译时加以限制。IsFlag<T>确定枚举是否已[FlagsAttribute]应用于它,并且CLR没有基于属性的约束。它将在一个完美的世界中-或存在其他方式来对其进行约束-但在这种情况下,它将不起作用:(
Jon Skeet

(尽管总则为+1,但我希望能够加以限制。)
乔恩·斯凯特


8

我愿意去NotSupportedException。虽然ArgumentException看起来不错,但确实可以预期传递给方法的参数是不可接受的。类型实参是您要调用的实际方法的定义特征,而不是实际的“实参”。InvalidOperationException当您正在执行的操作在某些情况下有效时抛出该异常,但对于特定情况,这是不可接受的。

NotSupportedException本质上不支持操作时抛出此错误。例如,当实现一个接口时,某个特定的成员对一个类没有意义。看起来类似的情况。


嗯 仍然感觉不太正确,但是我认为这将是最接近的事情。
乔恩·斯基特

乔恩:感觉不对,因为我们自然希望编译器能够捕获它。
Mehrdad Afshari

对。这是我想应用但不能使用的一种奇怪的约束:)
Jon Skeet

6

显然,Microsoft使用ArgumentException了该方法,如“ 例外”部分中的Expression.Lambda <>Enum.TryParse <>Marshal.GetDelegateForFunctionPointer <>的示例所示。我也找不到任何指示相反的示例(尽管在本地参考源中搜索TDelegateTEnum)。

因此,我认为可以安全地假设,至少在Microsoft代码中,ArgumentException除了基本变量之外,对无效的泛型类型参数使用通用的做法。鉴于docs中的异常描述没有区别这些异常描述,因此也没有太多困难。

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


框架中的单个示例对我来说是不够的,不是-考虑到我认为MS在其他情况下做出错误选择的地方的数量:)我不会TypeArgumentException从派生ArgumentException,仅仅是因为类型参数不是常规的论据。
乔恩·斯基特

1
就“这是MS始终如一地所做的事情”而言,这无疑更具吸引力。在匹配文档方面,它并没有变得更具吸引力……而且我知道C#团队中有很多人深切关注常规参数和类型参数之间的区别:)但是,感谢您提供的示例-他们非常有帮助。
乔恩·斯基特

@Jon Skeet:进行了编辑;现在,它包括3个来自不同MS库的示例,所有示例都记录了ArgumentException。因此,如果这是一个糟糕的选择,至少它是一贯的糟糕选择。;)我猜微软假设常规参数和类型参数都是参数;就个人而言,我认为这种假设是合理的。^^'
爱丽丝

啊,没关系,似乎您已经注意到了。很高兴我能帮助你。^^
爱丽丝

我认为我们必须同意就是否将它们相同对待是否合理持有不同意见。在反射或语言规则等方面,它们肯定是不一样的……它们的处理方式非常不同。
乔恩·斯基特


2

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


开发人员还应记录XML注释中引发的所有异常。
Eric Sc​​hneider

1

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


1

我总是对编写自定义异常持谨慎态度,纯粹是因为它们未始终清楚地记录在案,如果命名不正确,则会引起混乱。

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

如果用户试图传递不是枚举的内容,那么我将抛出InvalidOperationException。

编辑:

其他人提出了一个有趣的观点,即不支持此功能。我对NotSupportedException的唯一担心是,通常这些都是在将“暗物质”引入系统时抛出的异常,或者换句话说,“此方法必须在此接口上进入系统,但我们赢得了在2.4版之前将其打开”

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

编辑2:

另一个可能的:

System.ComponentModel.InvalidEnumArgumentException  

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


我会被限制为一个枚举(经过一些扑克游戏之后)-这只是我担心的标志。
乔恩·斯基特

我认为这些授权人员应该抛出LicensingException继承自的类的实例InvalidOperationException
Mehrdad Afshari

我同意Mehrdad的观点,不幸的是,例外是框架中存在很多灰色的领域之一。但是我敢肯定,许多语言都是一样的。(不是说我会回到vb6的运行时错误13呵呵)
彼得

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.