web-dev-qa-db-ja.com

型付き言語で不変性、共変性、反変性が必要な理由

私が正しいかどうか本当にわかりません。

C#でパラメーターとしてそのインターフェイスを渡すには、反変インターフェイスが必要であることを最近知りました。この機能は.NET 4.0でのみ追加されました。

したがって、明らかに、このCovariantまたはInvariantインターフェイスを実行できない理由がいくつかあります。これは、おそらく、ジェネリッククラスを渡して取得することと関係があります。

私はここでの制限が何であるか本当にわかりません、そして他の人がどこでCovariantを使用することができないところでContravariantインターフェイスが使用できるところを見ました(それが必要なのはいつですか?).

これはすべて好奇心から抜け出します(私はいくつかの言語を取り巻くプログラミング概念に非常に興味があります)。私はこれらの機能を使用する必要がある理由のいくつかの例を見てみたいと思います(共変/不変を反変シーンに、またその逆を許可すると何が壊れ、それぞれがどこで輝くか)。

7
Ingó Vals

共分散と反分散の必要性は、例で最もよく理解できます。

タイプList<Base>のパラメーターを受け入れる関数があるとします。ここで、Baseは他のクラスが継承する基本クラスです。 List<Derived1>Baseから継承されている場合、Derived1をこのルーチンに渡しても問題はないと直感的に考えると、ほとんどが正しいでしょう。それは問題ありません関数がリストを読み取り専用として処理した場合ですが、それ以外の場合は問題があります。

関数が認識するすべてのものがList<Base>であるため、理論的にはAddメソッドを呼び出し、Derived2インスタンスをリストに追加できます。これは、Base。そして、関数が戻り、Derived2オブジェクトのリストにDerived1があり、タイプセーフに違反しています。次に、リストを反復処理して、Derived2に存在しない各オブジェクトのメソッドを呼び出すと、全体が爆発します。

パラメータに追加の制約を適用することでこの種のシナリオを防止するために、共分散および反分散の要件があります。たとえば、この例では、リストに新しいオブジェクトを追加するメソッドを呼び出せないようにすることで、関数をタイプセーフにすることができます。コンパイラが分散要件を使用してそれを証明できる場合、このような関数を作成しても安全です。そうでなければ、そうではありません。

10
Mason Wheeler

C#でパラメーターとしてそのインターフェイスを渡すには、反変インターフェイスが必要であることを最近知りました。この機能は.NET 4.0でのみ追加されました。

.NET 4では、ジェネリックスのco/contra-varianceが追加されました。ただし、分散の概念は、サブタイピングを使用するすべての言語に存在します。

タイプチェックを実行するとき、パラメータタイプは、期待されるものと同じタイプまたはより具体的なタイプであれば許容されます。または単に、型は共変です。これは簡単です。メソッドがPersonで機能している場合、StudentまたはTeacherを渡すことは無関係です。

この機能の難点は、一般的なパラメーターです。場合によっては、ジェネリック型を共変的に使用しても安全です。たとえば、Action<Base>Action<Derived>型の変数に割り当てることができます。 Derivedが渡されたかどうかを気にする人は、Action<Base>を使用できます。

同様に、ジェネリック型を反変的に使用できる場合があります。Func<Derived>は、Func<Base>型の変数に割り当てることができます。 Derivedが常に返されるかどうかを気にする人は、Func<Base>を呼び出すものなら何でも結果を処理できます。

また、ジェネリック型を同時にまたは逆に使用できない場合があります。 List<T>は良い例です。一部の関数は正しい型ではないため、List<Derived>List<Base>に割り当てることはできません(またはその逆)。

基本的な考え方は、型システムが正しいということです。ユーザーが指定したジェネリックバリアンスを許可すると、ユーザーは、ジェネリック型をco/contra-variantにすることができる場所を型システムに伝えることができます。ただし、概念は、明示的な表記に関係なく、サブタイプのある型システムに存在します。

4
Telastyn

これは主に Eric Lippertによるこの優れたブログの書き直しです

C#サブタイプは、常にそれらの基本タイプとの割り当て互換性があります。 TeacherがPersonから派生している場合Person p = new Teacher();は有効です。つまり、真のIFF x = yが許可されている関係isAssignable(x,y)があります。

c#4以前は、サブタイプのジェネリックコレクションは基本タイプのジェネリックコレクションと割り当て互換性がありませんでした(つまり、IEnumerable<Teacher>IEnumerable<Person>から派生しないため、IEnumerable<Teacher>IEnumerable<Person>に割り当てることができませんでした)isAssignable(IEnumerable<x>,IEnumerable<y>)は、値に関係なく常にfalseでした/ isAssignable(x,y)

C#4はジェネリック型の分散を追加します。これにより、ジェネリック型は、T -> A<T>からのプロジェクションに対して* variantになります。つまり、共変パラメーターの場合、T,Uを保持する関係はA<T>,A<U>を保持し、逆変量の場合、関係は逆になります。つまり、関係isAssignable(x,y)の場合、IEnumerable<>sのように射影に対して共変である型は、それらのジェネリックパラメーターと同じ関係を持つため、isAssignable(x,y) == isAssignable(IEnumerable<x>,IEnumerable<y>)

2
jk.