他の人は、あなたのインターフェイスの難しさを指摘しています。それは、独自のクラスの他のアイテムと相互に動作できるクラスを明確に識別する方法がないということです。この問題は、そのようなクラスがリスコフの置換原理に違反しているという事実にある程度起因しています。クラスが baseQ 型の 2 つのオブジェクトを受け入れ、1 つのオブジェクトが相互に作用することを期待している場合、LSP は、baseQ オブジェクトの 1 つを派生 Q に置き換えることができるように指示します。これは、baseQ が派生 Q で動作する必要があり、派生 Q が baseQ で動作する必要があることを意味します。より広義には、baseQ の派生物は、baseQ の他の派生物に対して動作する必要があります。したがって、インターフェースは共変でも反変でも不変でもなく、むしろ非ジェネリックです。
ジェネリックを使用したい理由が、インターフェースがボクシングせずに構造体に作用できるようにすることである場合、phoog's answer に示されているパターンは良いものです。一般に、型パラメーターに再帰制約を課すことについて心配する必要はありません。インターフェイスの目的は、変数やパラメーターの型ではなく、制約としてではなく使用することであり、必要な条件は、制約を使用してルーチンによって課すことができるためです (例: VectorList<T,U> where T:IVector<T,U>
)。
ちなみに、制約として使用されるインターフェイス型の動作は、インターフェイス型の変数やパラメーターの動作とは大きく異なることに注意してください。すべての構造体型に対して、ValueType から派生した別の型があります。この後者のタイプは、値セマンティクスではなく参照セマンティクスを示します。値型の変数またはパラメーターがルーチンに渡されるか、クラス型を必要とする変数に格納される場合、システムはその内容を ValueType から派生した新しいクラス オブジェクトにコピーします。問題の構造体が不変である場合、そのようなすべてのコピーは常にオリジナルと同じコンテンツを保持し、互いに同じ内容を保持するため、一般的にオリジナルと意味的に同等であると見なすことができます。ただし、問題の構造体が可変の場合、そのようなコピー操作は、期待されるものとは非常に異なるセマンティクスをもたらす可能性があります。インターフェイス メソッドで構造体を変更すると便利な場合もありますが、そのようなインターフェイスは細心の注意を払って使用する必要があります。
たとえばList<T>.Enumerator
、 を実装する の動作を考えてみましょうIEnumerator<T>
。あるタイプの変数を同じタイプの別の変数にコピーList<T>.Enumerator
すると、リスト位置の「スナップショット」が取得されます。一方の変数で MoveNext を呼び出しても、もう一方の変数には影響しません。このような変数をObject
、IEnumerator<T>
、または から派生したインターフェイスのいずれかにコピーIEnumerator<T>
すると、スナップショットも取得されます。上記のように、元の変数または新しい変数のいずれかで MoveNext を呼び出しても、もう一方は影響を受けません。一方、タイプObject
、IEnumerator<T>
、または から派生したインターフェイスの 1 つの変数をIEnumerator<T>
、それらのタイプの 1 つである (同じまたは異なる) 別の変数にコピーすると、スナップショットは取得されず、以前に作成されたスナップショットへの参照がコピーされるだけです。
変数のすべてのコピーが意味的に同等であると便利な場合があります。それらが意味的に切り離されていると便利な場合もあります。残念ながら、注意を怠ると、「意味論的に紛らわしい」としか言いようのない意味論の奇妙な寄せ集めになってしまう可能性があります。