2011-07-12 6 views
3

vb.netでは、おそらく他の.net言語では、ジェネリッククラスのパラメータを取る例外クラスを定義してスローすることができます。たとえば、SomeThingBadHappenedException(Of T)を合法的に定義し、SomethingBadHappened(Of SomeType)をスローしてキャッチすることができます。これは、それぞれのコンストラクタを手動で定義することなく、例外ファミリーを作成する便利な方法を提供するようです。洗練された例外タイプは、コールスタックのさらに下にスローされる例外ではなく、実際に例外が予期している例外であることを保証するのに役立ちます。マイクロソフトでは、厳密に詳細なカスタム例外を使用する考え方は特に気にしないかもしれませんが、多くの既存の例外は予期せぬ場所から発生する可能性があります(たとえば、DLLからロードされるはずの関数を初めて呼び出すときに "FileNotFoundException" )カスタム例外をスローしてキャッチすることは、既存のものを使用することよりも安全だと思われます。ジェネリック型パラメータで例外を使用するのは良いか悪いですか

ジェネリッククラスの型パラメータが共変でも反逆(*)でもないため、SomethingBadHappened(Of SomeBaseType)としてのCatch ExceptionはSomethingBadHappened(OfDecandDerivedType)をキャッチしません。 。 SomethingBadHappened(Of U)から派生し、 "SomethingBadHappened(Of SomeDerivedType、SomeBaseType)"をスローするように "Catch Ex As SomethingBadHappened(Of T、U)"を定義することができますが、それはちょっとしたものです。 clunkyフォームまたは基本型を省略した形式(基本型の例外として捕らえられない)。

Generic型の例外を使用する考え方は何ですか? ?

(*)例外の派生型ではなくIException(Of Out T As Exception)の派生を捕捉できれば、共変の汎用例外タイプを定義することができます。もしIException(Of T)に "Self" propeが含まれていれば.netへT型のrty、Exceptionの派生Uをキャッチしようとすると、IException(Of U)もキャッチされますが、それは価値があるにはあまりに複雑すぎるでしょう。既存の例外階層で

補遺

、Fooクラスは、例えば、スローInvalidOperationExceptionは発生してはならないが、呼び出し元が処理しなければならないかもしれないが、予期せぬ状況に起因する多くの例外をキャッチせずに例外をキャッチする良い方法はありません。それはしないでください。クラスFooが独自の例外を定義することでその危険を避けることができますが、すべての "実際の"クラスでも1つのカスタム例外クラスが定義されていれば、カスタム例外クラスはすぐに圧倒される可能性があります。 CleanFailureException(Of Foo)のように何らかの理由で要求された操作が行われなかったが状態が妨害されていないことを示す場合はCleanerFailureException(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

少なくとも一つの利点は、1つがリフレクションを使用しない限り、作成する例外を定義するジェネリック型パラメータを持つ便利な例外の工場を持ってすることはできませんしながら、多少不快な方法ではありますが、ジェネリック型パラメータを含む例外クラスを作成することは可能です。誰かが興味を持っているなら、私はそれを含むコード例を更新することができます。

それ以外の場合、派生クラスごとに同じコンストラクターコードを繰り返さずにカスタム例外を定義するためのまともなコーディングパターンはありますか?私は本当にvb.netおよび/またはc#が単一のパラメータのないクラス固有のコンストラクタを指定し、各親のオーバーロードに対してパブリックコンストラクタを自動的に作成する構文を含むことを望みます。

さらにいくつかの検討に補遺3

は、それは本当に多くの場合、必要なものがスローされた例外がクラスに縛られていないことであるように思えるが、むしろ例外との間で定義された関係を持っていますオブジェクトインスタンス。残念ながら、catch SomeExceptionType(ThisParticularFoo)の概念を定義する明確な方法はありません。最善の策は、 "NoCorruptionOutisde"述部を持つカスタム基本例外クラスを定義し、 "Ex.NoCorruptionOutside(MyObjectInstance)"を指定したときにCorruptObjectExceptionをキャッチすることです。それはどうやって聞こえる?

+0

この点は何ですか?コード例は、急いでそれをクリアするでしょう。 –

+0

@Chris Shouts:編集例です。 – supercat

答えて

1

家族からのすべての種類の例外をキャッチする必要がある場合はどうすればよいですか?あなたはそれをすることができませんでした。したがって、ジェネリックのポイントは、異なるタイプを使用するオブジェクト間の意味の類似性を捕らえることです。コレクション意味論、検索意味論など の場合は、catch (MyGenericException<T> e)は可能ではないため、反対にする必要があります。正確なタイプを指定する必要があります。したがって、ほとんどの場合、私はちょうど利点を見ていないと言いました。

WCFで私にとっての正しい使い方は、FaultException<T>です。Tは、あらかじめWCFライターには未知のデータ型であり、何でもかまいません。しかし、実際には、これらのFaultExceptionsをキャッチして意味のある階層で手作業で構築された関数に変換する自動生成関数がありました。

私はこれが「大きなノー」ではないとは言えませんが、現時点では申し訳ありません。

UPDATE:キャッチするインターフェイスと同じです。 Exceptionから派生したものだけを捕まえることができるので、それは不可能です。インターフェイスは明らかにそうではありません。補遺ため

UPDATE:

それは私のために良いアイデアのように見えるしていません。

最初はすごく複雑です。このような入れ子のジェネリック、継承、クラスの混合を理解するための精神的負担...まあ、それは私にとってはあまりにも多くのことです。反対の利点は非常に謙虚です。

第2の - あなたは非常にまれに、すべてについてのそのような正確な知識が必要です。エラーを投げたクラスを正確に理解していますか?最悪の場合にはコールスタックからの呼び出しがあります。実際、コールスタックにはさらに多くの機能があります。正確なサブタイプのエラー?どうして?あなたが何らかの例外を予期しているなら、通常は周囲のコードから正確な詳細を知っています。さもなければ、これらのジェネリックはあなたを少し助けません。

第3の点は、どこでもcatch (Exception)のような悪いコードの場合、そのようなアプローチが大声で叫ぶことです。他の方法はないから。

例を挙げておきます。ファイル操作を使用している場合、通常はすべてが良いとみなして、ちょうどcatch (IOException e)のどこかが最初に上がったとみなすことができます。一度。ここであなたのアプローチを使用しているとします。役に立つものを実装しているutilスタティッククラスFileUtilもあるとします。あなたの例によると(少なくとも私が正しく理解していれば)Fault<FileUtil>のような例外がスローされます。したがって、呼び出しコードは、1)FileUtilが使用されており、対応する例外が予想されるか、または2)一般的なキャッチファンクションExceptionが「不良」であることがわかっている必要があります。しかし、私たちのFileUtilは実装の詳細です。私たちはそれを変更するオプションを持っていて、呼び出しコードには影響しません!それはただ不可能です。

これは、javaのチェック例外機能を思い出させます。 throws句を変更すると、すべての呼び出しコードが変更されます。本当に良いことではありません。

+0

編集した質問をご覧ください。うまくいけば、あなたはvb.netを読むことができます。もしそうでなければ、私はおそらくC#で何を意味するのか説明することができます。 – supercat

+0

私はあなたの補遺のためのまだ1つのアップデートで私の答えを更新しました。 –

+0

私は可能な利点(ジェネリック例外ファクトリー)で付録2を追加しました。明示的な型を使用して例外が使用された場合、実際にはJavaのチェック例外のようになり、例外をキャッチして上のレイヤーにCleanFailureまたはさまざまな程度のCorruptFailureとして変換する必要があります。おそらく、中間層が翻訳なしで例外をエスケープすることがない場合、例外をカスタム型に変換する必要はありませんが、InvalidOperationExceptionのようなものは少し汚れているようです。 – supercat

0

これらの一般的な例外で何を達成したいのかよく分かりませんが、.NETがcatch節の共分散を認識しないと言っていると思いますか?もしそうならば、この問題に対する部分的な解決策は、非ジェネリックベース定義するには、次のようになります。

public class MyException : Exception 
{ 
    protected MyException() {} 
    public abstract object Data { get; } 
} 
public class MyException<T> : MyException 
{ 
    private T _data; 
    public MyException(T data) { _data = data; } 
    // Oops, .NET doesn't allow return type covariance, so... define 2 properties? 
    public override object Data { get { return _data; } } 
    public    T DataT { get { return _data; } } 
} 

を今、少なくとも、あなたはMyExceptionをキャッチし、BaseClassまたはDerivedClassDataをキャストしようとするオプションがあります。

+0

はい、可能です。疑問はなぜジェネリック医薬品を最初に持っているのか? 2つの異なるケース(あなたの 'Data'をキャストすることができる)が2つしかない場合、2つの異なる非ジェネリッククラスと2つの' catch'セクションを持たないのはなぜですか? –

2

これはあなたが本当に必要以上に困難になっているようです。私はこのような例外クラスを作るお勧めします:

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と呼ばれ、別のコンストラクターを提供します。特定のFaultTypeのオブジェクトに余分なデータを格納する必要がある場合は、CleanFailureExceptionを継承し、そのFaultTypeに固有の余分なデータのプロパティを追加するだけです。例外ハンドラで余分なデータプロパティが必要な場合は、そのサブクラスの例外をキャッチするか、そうでない場合は単にCleanFailureExceptionをキャッチすることができます。私はジェネリックスが例外のために使われているのを見たことはありませんでしたが、私はかなり確信しています。

+0

上記の補遺2で述べたように、私は、一般的な例外タイプ(一般的なファクトリがそれを作成できる)を使用することの少なくとも1つの利点を考えました。 vb.netの場合、キャッチすべきかどうかを判断するメソッドを含むcatch-all例外タイプを1つだけ持つ方がよいでしょう。そのようなアプローチは、継承を使用して例外をキャッチするかどうかを判断するよりもはるかに洗練されていますが、完全に非CLSに準拠しています。 – supercat

関連する問題