.net - 使用带有泛型类型参数的异常是好是坏

标签 .net vb.net generics exception

在vb.net以及可能的其他.net语言中,可以定义和引发采用通用类参数的异常类。例如,可以合法地定义SomeThingBadHappenedException(Of T),然后抛出并捕获SomethingBadHappened(Of SomeType)。这似乎提供了一种方便的方式来制作一系列异常,而不必为每个异常手动定义构造函数。改进异常类型似乎有助于确保一个捕获的异常实际上是一个预期的异常,而不是成为在调用堆栈中进一步抛出的异常。 Microsoft可能不特别喜欢使用细节严格的自定义异常的想法,但是由于许多预先存在的异常可能来自意外位置(例如,“FileNotFoundException”在第一次调用应该从DLL加载的函数时) )引发和捕获自定义异常似乎比使用现有异常更安全。

我看到的关于自定义异常的最大限制是,由于泛型类参数既不是协变也不是协变(*),所以“将Ex捕获为SomethingBadHappened(Of SomeBaseType)”不会捕获SomethingBadHappened(Of SomeDerivedType)。可以将“将Ex捕获为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)继承,指示失败的更精确原因。

我还没有找到使用通用异常的任何方法,这些方法最终不会让您感到笨拙,但这并不意味着没有其他人可以找到更好的方法。请注意,派生的异常类型可以正常工作。唯一真正的麻烦是,每当引发或捕获故障时,都必须指定导数链中的所有内容。

'定义一些接口(interface),用于指示哪些故障是由其他故障引起的
接口(interface)IFault(T)
终端接口(interface)
接口(interface)IFault(T,U作为IFault(T))
终端接口(interface)
接口(interface)IFault(T,U作为IFault(T),V As IFault(T,U))
终端接口(interface)

派生异常本身。当然,实际代码应包括所有构造函数。
类CleanFailureException
继承异常
子新建(ByVal消息为字符串,ByVal innerException为异常)
MyBase.New(Msg,innerException)
结束子
末级
类CleanFailureException(Of T)
继承CleanFailureException
子新建(ByVal消息为字符串,ByVal innerException为异常)
MyBase.New(Msg,innerException)
结束子
末级
类CleanFailureException(Of T,FaultType如IFault(Of T))
继承CleanFailureException(Of T)
子新建(ByVal消息为字符串,ByVal innerException为异常)
MyBase.New(Msg,innerException)
结束子
末级
类CleanFailureException(Of T,FaultType如IFault(Of T),FaultSubType As IFault(Of T,FaultType))
继承CleanFailureException(Of T,FaultType)
子新建(ByVal消息为字符串,ByVal innerException为异常)
MyBase.New(Msg,innerException)
结束子
末级
类CleanFailureException(T,FaultType作为IFault(T),FaultSubType作为IFault(T,FaultType),FaultSubSubType作为IFault(T,FaultType,FaultSubType))
继承CleanFailureException(Of T,FaultType,FaultSubType)
子新建(ByVal消息为字符串,ByVal innerException为异常)
MyBase.New(Msg,innerException)
结束子
末级

'现在是使用此类异常的示例类
类FileLoader
类错误'有效地用作类中的 namespace
类FileParsingError
实现IFault(Of FileLoader)
末级
类InvalidDigit
实现IFault(Of FileLoader,FileParsingError)
末级
当我要AZeroOrOne时,类GotADollarSign
实现IFault(FileLoader,FileParsingError,InvalidDigit)
末级
类别GotAPercentSignWhenIWantedASix
实现IFault(FileLoader,FileParsingError,InvalidDigit)
末级
类InvalidSeparator
实现IFault(Of FileLoader,FileParsingError)
末级
类SomeOtherError
实现IFault(Of FileLoader)
末级
末级

'现在测试例程抛出上述异常

共享子TestThrow(ByVal WhereOne作为整数)
选择案例
案例0
抛出新的CleanFailureException(Of FileLoader,Faults.FileParsingError,Faults.InvalidDigit,Faults.GotADollarSignWhenIWantedAZeroOrOne)_
(“糟糕”,什么都没有)
情况1
抛出新的CleanFailureException(Of FileLoader,Faults.FileParsingError,Faults.InvalidDigit,Faults.GotAPercentSignWhenIWantedASix)_
(“糟糕”,什么都没有)
情况二
抛出新的CleanFailureException(Of FileLoader,Faults.FileParsingError,Faults.InvalidDigit)_
(“糟糕”,什么都没有)
情况二
抛出新的CleanFailureException(Of FileLoader,Faults.FileParsingError,Faults.InvalidSeparator)_
(“糟糕”,什么都没有)
案例4
抛出新的CleanFailureException(Of FileLoader,Faults.FileParsingError)_
(“糟糕”,什么都没有)
案例5
抛出新的CleanFailureException(Of FileLoader,Faults.SomeOtherError)_
(“糟糕”,什么都没有)
案例6
抛出新的CleanFailureException(Of FileLoader)_
(“糟糕”,什么都没有)
案例7
抛出新的CleanFailureException(Integer)_
(“糟糕”,什么都没有)
结束选择
结束子

'查看如何捕获每种异常类型的例程
共享子TestFaults()
对于i作为整数= 0到7
尝试
TestThrow(i)
以CleanFailureException(FileLoader,Faults.FileParsingError,Faults.InvalidDigit,Faults.GotADollarSignWhenIWantedAZeroOrOne)形式捕获
Debug.Print(“将{0}捕获为GotADollarSignWhenIWantedAZeroOrOne”,例如GetType)
捕获为CleanFailureException(Of FileLoader,Faults.FileParsingError,Faults.InvalidDigit)
Debug.Print(“将{0}捕获为InvalidDigit”,例如,GetType)
捕获为CleanFailureException(Of FileLoader,Faults.FileParsingError)
Debug.Print(“{0}被捕获为FileParsingError”,例如,GetType)
捕获为CleanFailureException(FileLoader)
Debug.Print(“将{0}捕获为FileLoader”,例如,GetType)
捕获为CleanFailureException
Debug.Print(“将{0}捕获为CleanFailureException”,例如,GetType)
结束尝试
下一个
结束子
末级

附录2

使用泛型异常的至少一个优点是,虽然无法使用有用的异常工厂来定义要创建的异常的泛型类型参数,除非人们以某种​​令人反感的方式使用了Reflection,但是有可能让工厂创建一个包含通用类型参数的异常类。如果有人感兴趣,我可以更新一个代码示例以包括该代码示例。

否则,是否有任何不错的编码模式来定义自定义异常,而不必为每个不同的派生类重复相同的构造函数代码?我真的希望vb.net和/或C#包含一种语法,以指定单个无参数的特定于类的构造函数,并为每个父级重载自动创建 public 构造函数。

附录3

经过进一步的考虑,似乎在很多情况下,真正需要的不是将引发的异常绑定(bind)到类,而是在异常和对象实例之间定义了关系。不幸的是,没有干净的方法来定义“catch SomeExceptionType(ThisParticularFoo)”的概念。最好的选择是用“NoCorruptionOutisde”谓词定义一个自定义基本异常类,然后说“Ex.NoCorruptionOutside(MyObjectInstance)时将Ex捕获为CorruptObjectException”。听上去怎么样?

最佳答案

看来您确实在使这项工作变得比所需的困难。我建议制作这样的异常类:

Public Class CleanFailureException
    Inherits Exception

    Public Enum FaultType
        Unknown
        FileParsingError
        InvalidDigit
        WhateverElse
    End Enum

    Public Property FaultReason As FaultType

    Public Sub New(msg As String, faultReason As FaultType, innerException As Exception)
        MyBase.New(msg, innerException)
        Me.FaultReason = faultReason
    End Sub
End Class

然后,在您的异常处理代码中,执行以下操作:
Try
   SomeAction()
Catch cfex As CleanFailureException
   Select Case cfex.FaultReason
       Case CleanFailureException.FaultType.FileParsingError
           ' Handle error
       Case Else
           Throw ' don't throw cfex so you preserve stack trace
   End Select
Catch ex As AnyOtherException
    ' Handle this somehow
End Try

如果需要错误类型/子类型,可以添加另一个名为SecondaryFaultReason的属性并提供另一个构造函数。如果您需要为某些FaultTypes在对象上存储一些额外的数据,只需继承CleanFailureException,然后为该FaultType特定的额外数据添加属性。然后,如果需要在异常处理程序中获取额外的数据属性,则可以捕获该子类异常,或者如果不需要,则可以捕获CleanFailureException。我从未见过将泛型用于例外情况,而且我非常确信即使有充分的理由在某个地方进行此操作,您所解释的也不是。

关于.net - 使用带有泛型类型参数的异常是好是坏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6671996/

相关文章:

.net - 错误 : The assembly "c:\MyAssembly.dll" could not be converted to a type library

javascript - 浏览器不显示文件已下载

c# - 使用通用属性名称处理 lambda 表达式

VB.Net 使用属性名称对列表进行排序

sql - VB.Net - SQLDataReader 中的错误和 "Conversion from type ' DBNULL' 键入 'Date' 无效

Typescript:键与值类型相同的映射,不限制键的类型

generics - Kotlin 泛型 : counterintuitive type inference and checking with out keyword

ASP.NET 网站还是 ASP.NET Web 应用程序?

.net - Entity Framework /MVC3 : temporarily disable validation

c# - 请说明如何在.Net 4.6中使用EventListener类