【发布时间】:2011-10-04 01:26:27
【问题描述】:
在 vb.net 和可能的其他 .net 语言中,可以定义和抛出采用通用类参数的异常类。例如,可以合法地定义 SomeThingBadHappenedException(Of T),然后抛出并捕获 SomethingBadHappened(Of SomeType)。这似乎提供了一种方便的方法来生成一系列异常,而无需为每个异常手动定义构造函数。优化异常类型似乎有助于确保捕获的异常实际上是预期的异常,而不是被进一步抛出调用堆栈的异常。 Microsoft 可能并不特别喜欢使用详细的自定义异常的想法,但由于许多预先存在的异常可能来自意想不到的地方(例如,第一次调用应该从 DLL 加载的函数时出现“FileNotFoundException” ) 抛出和捕获自定义异常似乎比使用现有异常更安全。
我看到的自定义异常的最大限制是,由于泛型类类型参数既不是协变也不是逆变(*),“Catch Ex as SomethingBadHappened(Of SomeBaseType)”不会捕捉到 SomethingBadHappened(Of SomeDerivedType)。可以将“Catch Ex As SomethingBadHappened(Of T,U)”定义为从 SomethingBadHappened(Of U) 派生,从而抛出“SomethingBadHappened(Of SomeDerivedType, SomeBaseType) 但这有点笨拙,并且必须始终使用其中一个笨拙的形式或省略基本类型的形式(并且不能作为基本类型异常捕获)。
人们如何看待使用通用类型异常的想法?除了上面提到的还有什么陷阱吗?
(*) 如果可以捕获 IException(Of Out T As Exception) 的派生类,而不仅仅是 Exception 的派生类,则可以定义协变的泛型异常类型。如果 IException(Of T) 包含 T 类型的“Self”属性,Microsoft 可能会将这种能力强加到 .net 中,并且尝试捕获 Exception 的派生 U 也会捕获任何 IException(Of U),但这可能太复杂了,不值得。
附录
在现有的异常层次结构中,如果类 Foo 抛出例如InvalidOperationException 在某些不应该发生但调用者可能必须处理的特定条件下,调用者没有很好的方法来捕获这些异常而不捕获由不可预见的条件导致的大量异常,其中一些应该被捕获,而其他的这不应该。让类 Foo 定义它自己的异常可以避免这种危险,但是如果每个“真正的”类都定义了一个自定义异常类,那么自定义异常类可能很快就会变得不堪重负。有类似 CleanFailureException(Of Foo) 的东西似乎更干净,表明请求的操作由于某种原因没有发生但状态没有受到干扰,或者 CleanFailureException(Of Foo, Foo.Causes.KeyDuplicated),这将继承自 CleanFailureException(Of Foo),表示更准确的失败原因。
我还没有想出任何使用通用异常的方法,它最终不会让人觉得有点笨拙,但这并不意味着没有其他人能找到更好的方法。请注意,派生的异常类型可以正常工作;唯一真正的烦恼是必须在任何时候抛出或捕获错误时指定派生链上的所有内容。
' 定义一些接口,用于指示哪些故障源自其他故障 接口 IF(Of T) 端接口 接口 IFault(Of T, U As IFault(Of T)) 端接口 接口 IFault(Of T, U As IFault(Of T), V As IFault(Of T, U)) 端接口 ' 自己推导出异常。当然,真正的代码应该包括所有的构造函数。 类 CleanFailureException 继承异常 Sub New(ByVal Msg As String, ByVal innerException As Exception) MyBase.New(消息,innerException) 结束子 结束类 类 CleanFailureException(Of T) 继承 CleanFailureException Sub New(ByVal Msg As String, ByVal innerException As Exception) MyBase.New(消息,innerException) 结束子 结束类 类 CleanFailureException(Of T, FaultType As IFault(Of T)) 继承 CleanFailureException(Of T) Sub New(ByVal Msg As String, ByVal innerException As Exception) MyBase.New(消息,innerException) 结束子 结束类 类 CleanFailureException(Of T, FaultType As IFault(Of T), FaultSubType As IFault(Of T, FaultType)) 继承 CleanFailureException(Of T, FaultType) Sub New(ByVal Msg As String, ByVal innerException As Exception) MyBase.New(消息,innerException) 结束子 结束类 类 CleanFailureException(Of T, FaultType As IFault(Of T), FaultSubType As IFault(Of T, FaultType), FaultSubSubType As IFault(Of T, FaultType, FaultSubType)) 继承 CleanFailureException(Of T, FaultType, FaultSubType) Sub New(ByVal Msg As String, ByVal innerException As Exception) MyBase.New(消息,innerException) 结束子 结束类 ' 现在是使用此类异常的示例类 类文件加载器 Class Faults ' 有效地用作类中的命名空间 类 FileParsingError 实现 IFault(Of FileLoader) 结束类 类 InvalidDigit 实现 IFault(Of FileLoader, FileParsingError) 结束类 类 GotAdollarSignWhenIWantedAZero 或 One 实现 IFault(Of FileLoader, FileParsingError, InvalidDigit) 结束类 类 GotAPercentSignWhenIWantedASix 实现 IFault(Of FileLoader, FileParsingError, InvalidDigit) 结束类 类 InvalidSeparator 实现 IFault(Of FileLoader, FileParsingError) 结束类 类 SomeOtherError 实现 IFault(Of FileLoader) 结束类 结束类 ' 现在是一个抛出上述异常的测试例程 Shared Sub TestThrow(ByVal WhichOne As Integer) 选择案例whichOne 案例 0 抛出新的 CleanFailureException(FileLoader,Faults.FileParsingError,Faults.InvalidDigit,Faults.GotADollarSignWhenIWantedAZeroOrOne)_ (“哎呀”,什么都没有) 情况1 抛出新的 CleanFailureException(FileLoader,Faults.FileParsingError,Faults.InvalidDigit,Faults.GotAPercentSignWhenIWantedASix)_ (“哎呀”,什么都没有) 案例2 抛出新的 CleanFailureException(FileLoader,Faults.FileParsingError,Faults.InvalidDigit)_ (“哎呀”,什么都没有) 案例2 抛出新的 CleanFailureException(FileLoader,Faults.FileParsingError,Faults.InvalidSeparator)_ (“哎呀”,什么都没有) 案例4 抛出新的 CleanFailureException(Of FileLoader, Faults.FileParsingError) _ (“哎呀”,什么都没有) 案例5 抛出新的 CleanFailureException(Of FileLoader, Faults.SomeOtherError) _ (“哎呀”,什么都没有) 案例6 抛出新的 CleanFailureException(Of FileLoader) _ (“哎呀”,什么都没有) 案例7 抛出新的 CleanFailureException(Of Integer) _ (“哎呀”,什么都没有) 结束选择 结束子 ' 查看每个异常类型如何被捕获的例程 共享子 TestFaults() 对于 i 作为整数 = 0 到 7 尝试 测试投掷(一) 捕获 ex 作为 CleanFailureException(FileLoader,Faults.FileParsingError,Faults.InvalidDigit,Faults.GotADollarSignWhenIWantedAZeroOrOne) Debug.Print("Caught {0} as GotADollarSignWhenIWantedAZeroOrOne", ex.GetType) 捕获 ex 作为 CleanFailureException(FileLoader,Faults.FileParsingError,Faults.InvalidDigit) Debug.Print("Caught {0} as InvalidDigit", ex.GetType) 将 ex 捕获为 CleanFailureException(FileLoader,Faults.FileParsingError) Debug.Print("Caught {0} as FileParsingError", ex.GetType) 将 ex 捕获为 CleanFailureException(Of FileLoader) Debug.Print("Caught {0} as FileLoader", ex.GetType) 将 ex 作为 CleanFailureException 捕获 Debug.Print("Caught {0} as CleanFailureException", ex.GetType) 结束尝试 下一个 结束子 结束类附录 2
使用泛型异常的至少一个优点是,虽然不可能有一个有用的异常工厂,它有一个泛型类型参数来定义要创建的异常,除非以某种令人反感的方式使用反射,但可以有一个工厂创建一个包含泛型类型参数的异常类。如果有人感兴趣,我可以更新一个代码示例来包含它。
否则,是否有任何体面的编码模式来定义自定义异常,而不必为每个不同的派生类重复相同的构造函数代码?我真的希望 vb.net 和/或 c# 包含一种语法来指定单个无参数的特定于类的构造函数,并为每个父重载自动创建公共构造函数。
附录 3
进一步考虑,在许多情况下,似乎真正需要的不是将抛出的异常绑定到类,而是在异常和对象实例之间建立已定义的关系。不幸的是,没有明确的方法来定义“catch SomeExceptionType(ThisParticularFoo)”的概念。最好的选择可能是使用“NoCorruptionOutisde”谓词定义自定义基异常类,然后说“当 Ex.NoCorruptionOutside(MyObjectInstance) 时捕获 Ex As CorruptObjectException”。听起来怎么样?
【问题讨论】:
-
这是什么意思?一个代码示例会很快把它弄清楚。
-
@Chris Shouts:编辑示例。
标签: .net vb.net generics exception