В vb.net и, предположительно, в других языках .net можно определять и генерировать классы исключений, которые принимают универсальные параметры класса.Например, можно юридически определить SomeThingBadHappenedException (Of T), а затем бросить и поймать SomethingBadHappened (Of SomeType).Казалось бы, это обеспечивает удобный способ создания семейства исключений без необходимости вручную определять конструкторы для каждого из них.Уточнение типов исключений может показаться полезным для гарантии того, что исключение, которое один перехватывает, на самом деле является ожидаемым исключением, а не исключением, генерируемым дальше по стеку вызовов.Microsoft, возможно, не особенно нравится идея использования тщательно детализированных пользовательских исключений, но так как многие ранее существующие исключения могут возникать из неожиданных мест (например, «FileNotFoundException» при первом вызове функции, которая должна быть загружена из DLL)) бросать и перехватывать пользовательские исключения кажется более безопасным, чем использование существующих.
Самое большое ограничение, которое я вижу с пользовательскими исключениями, заключается в том, что, поскольку параметры типа универсального класса не являются ни ковариантными, ни контравариантными (*), «Catch Ex какSomethingBadHappened (Of SomeBaseType) "не будет перехватывать SomethingBadHappened (Of SomeDerivedType).Можно определить «Catch Ex As SomethingBadHappened (Of T, U)» как производный от SomethingBadHappened (Of U), и, таким образом, выдать «SomethingBadHappened (Of SomeDerivedType, SomeBaseType), но это немного неуклюже, и нужно будет последовательно использовать либонеуклюжая форма или форма, которая пропускает базовый тип (и не может быть поймана как исключение базового типа).
Что люди думают об идее использования исключений общего типа? Есть ли какие-то другие ошибки?чем упомянуто выше?
(*) Если можно поймать производные IException (Out Out T As Exception), а не просто производные Exception, можно определить ковариантные универсальные типы исключений. Возможно, Microsoft сможетдобавьте такую возможность в .net, если IException (Of T) включает свойство «Self» типа T, и попытка перехватить производную U от Exception также перехватит любое IException (Of U), но это, вероятно, будет слишком сложнобыть стоящим.
Приложение
На выходеsting иерархия исключений, если класс Foo генерирует, например, InvalidOperationException при каком-то конкретном условии, которое не должно происходить, но с которым, возможно, придется обращаться вызывающей стороне, вызывающий вызов не может найти эти исключения, не отлавливая также множество исключений, являющихся результатом непредвиденных условий,некоторые из которых должны быть пойманы, а другие нет.Наличие класса Foo для определения собственных исключений позволит избежать этой опасности, но если бы каждый «настоящий» класс определил хотя бы один пользовательский класс исключений, пользовательские классы исключений могли бы быстро стать подавляющими.Казалось бы, намного чище иметь что-то вроде CleanFailureException (Of Foo), указывающее, что запрошенная операция не произошла по какой-то причине, но состояние не нарушено, или CleanFailureException (Of Foo, Foo.Causes.KeyDuplicated), которыйНаследовать от CleanFailureException (Of Foo), указывая более точную причину сбоя.
Я не нашел никаких средств использования обобщенных исключений, которые в итоге не кажутся немного неуклюжими, но это не так.Я имею в виду, что никто не мог найти лучший путь.Обратите внимание, что производные типы исключений работают так, как должны;единственное реальное раздражение - это указывать все в цепочке деривации каждый раз, когда происходит сбой или обнаружена ошибка.
' Define some interfaces which are used to indicate which faults derive from others
Interface IFault(Of T)
End Interface
Interface IFault(Of T, U As IFault(Of T))
End Interface
Interface IFault(Of T, U As IFault(Of T), V As IFault(Of T, U))
End Interface
' Derive the exceptions themselves. Real code should include all the constructors, of course.
Class CleanFailureException
Inherits Exception
Sub New(ByVal Msg As String, ByVal innerException As Exception)
MyBase.New(Msg, innerException)
End Sub
End Class
Class CleanFailureException(Of T)
Inherits CleanFailureException
Sub New(ByVal Msg As String, ByVal innerException As Exception)
MyBase.New(Msg, innerException)
End Sub
End Class
Class CleanFailureException(Of T, FaultType As IFault(Of T))
Inherits CleanFailureException(Of T)
Sub New(ByVal Msg As String, ByVal innerException As Exception)
MyBase.New(Msg, innerException)
End Sub
End Class
Class CleanFailureException(Of T, FaultType As IFault(Of T), FaultSubType As IFault(Of T, FaultType))
Inherits CleanFailureException(Of T, FaultType)
Sub New(ByVal Msg As String, ByVal innerException As Exception)
MyBase.New(Msg, innerException)
End Sub
End Class
Class CleanFailureException(Of T, FaultType As IFault(Of T), FaultSubType As IFault(Of T, FaultType), FaultSubSubType As IFault(Of T, FaultType, FaultSubType))
Inherits CleanFailureException(Of T, FaultType, FaultSubType)
Sub New(ByVal Msg As String, ByVal innerException As Exception)
MyBase.New(Msg, innerException)
End Sub
End Class
' Now a sample class to use such exceptions
Class FileLoader
Class Faults ' Effectively used as a namespace within a class
Class FileParsingError
Implements IFault(Of FileLoader)
End Class
Class InvalidDigit
Implements IFault(Of FileLoader, FileParsingError)
End Class
Class GotADollarSignWhenIWantedAZeroOrOne
Implements IFault(Of FileLoader, FileParsingError, InvalidDigit)
End Class
Class GotAPercentSignWhenIWantedASix
Implements IFault(Of FileLoader, FileParsingError, InvalidDigit)
End Class
Class InvalidSeparator
Implements IFault(Of FileLoader, FileParsingError)
End Class
Class SomeOtherError
Implements IFault(Of FileLoader)
End Class
End Class
' Now a test routine to throw the above exceptions
Shared Sub TestThrow(ByVal WhichOne As Integer)
Select Case WhichOne
Case 0
Throw New CleanFailureException(Of FileLoader, Faults.FileParsingError, Faults.InvalidDigit, Faults.GotADollarSignWhenIWantedAZeroOrOne) _
("Oops", Nothing)
Case 1
Throw New CleanFailureException(Of FileLoader, Faults.FileParsingError, Faults.InvalidDigit, Faults.GotAPercentSignWhenIWantedASix) _
("Oops", Nothing)
Case 2
Throw New CleanFailureException(Of FileLoader, Faults.FileParsingError, Faults.InvalidDigit) _
("Oops", Nothing)
Case 2
Throw New CleanFailureException(Of FileLoader, Faults.FileParsingError, Faults.InvalidSeparator) _
("Oops", Nothing)
Case 4
Throw New CleanFailureException(Of FileLoader, Faults.FileParsingError) _
("Oops", Nothing)
Case 5
Throw New CleanFailureException(Of FileLoader, Faults.SomeOtherError) _
("Oops", Nothing)
Case 6
Throw New CleanFailureException(Of FileLoader) _
("Oops", Nothing)
Case 7
Throw New CleanFailureException(Of Integer) _
("Oops", Nothing)
End Select
End Sub
' A routine to see how each exception type gets caught
Shared Sub TestFaults()
For i As Integer = 0 To 7
Try
TestThrow(i)
Catch ex As CleanFailureException(Of FileLoader, Faults.FileParsingError, Faults.InvalidDigit, Faults.GotADollarSignWhenIWantedAZeroOrOne)
Debug.Print("Caught {0} as GotADollarSignWhenIWantedAZeroOrOne", ex.GetType)
Catch ex As CleanFailureException(Of FileLoader, Faults.FileParsingError, Faults.InvalidDigit)
Debug.Print("Caught {0} as InvalidDigit", ex.GetType)
Catch ex As CleanFailureException(Of FileLoader, Faults.FileParsingError)
Debug.Print("Caught {0} as FileParsingError", ex.GetType)
Catch ex As CleanFailureException(Of FileLoader)
Debug.Print("Caught {0} as FileLoader", ex.GetType)
Catch ex As CleanFailureException
Debug.Print("Caught {0} as CleanFailureException", ex.GetType)
End Try
Next
End Sub
End Class
Приложение 2
По крайней мере одно преимущество использования универсальных исключений состоит в том, что, хотя невозможно иметь полезную фабрику исключений с параметром универсального типа, определяющим создаваемое исключение, если только не используется Reflection в несколько неприятном виде, но возможно иметь Фабрика создает класс исключений, который включает параметр общего типа. Если кому-то интересно, я мог бы обновить пример кода, чтобы включить это.
Иначе, существует ли какой-либо достойный шаблон кодирования для определения пользовательских исключений без необходимости повторять один и тот же код конструктора для каждого другого производного класса? Я действительно хотел бы, чтобы vb.net и / или c # включали синтаксис для определения одного безпараметрического конструктора, специфичного для класса, и автоматически создавали публичные конструкторы для каждой родительской перегрузки.
Приложение 3
При дальнейшем рассмотрении кажется, что во многих случаях действительно необходимо не привязывать выброшенное исключение к классу, а иметь определенную связь между исключением и экземпляром объекта. К сожалению, нет четкого способа определить концепцию «catch SomeExceptionType (ThisParticularFoo)». Лучше всего было бы определить пользовательский базовый класс исключений с предикатом «NoCorruptionOutisde», а затем сказать «Catch Ex As CorruptObjectException When Ex.NoCorruptionOutside (MyObjectInstance)». Как это звучит?