web-dev-qa-db-ja.com

共変性と反変性の簡単な例

誰かが私に共変性、反変性、不変性、および反不変の簡単なC#の例を教えてもらえますか(そのようなものが存在する場合)。

これまでに見たすべてのサンプルは、オブジェクトをSystem.Objectにキャストするだけでした。

42
Grant Smith

誰かが私に共変性、反変性、不変性、および反不変の簡単なC#の例を教えてもらえますか(そのようなものが存在する場合)。

「逆不変性」が何を意味するのかわかりません。残りは簡単です。

共分散の例を次に示します。

void FeedTheAnimals(IEnumerable<Animal> animals) 
{ 
    foreach(Animal animal in animals)
        animal.Feed();
}
...
List<Giraffe> giraffes = ...;
FeedTheAnimals(giraffes);

IEnumerable<T>インターフェースは共変です。キリンが動物に変換可能であるという事実は、IEnumerable<Giraffe>IEnumerable<Animal>に変換可能であることを意味します。 List<Giraffe>IEnumerable<Giraffe>を実装しているため、このコードはC#4で成功します。 IEnumerable<T>の共分散がC#3で機能しなかったため、C#3では失敗していました。

これは理にかなっているはずです。キリンのシーケンスは、動物のシーケンスとして扱うことができます。

共変性の例を次に示します。

void DoSomethingToAFrog(Action<Frog> action, Frog frog)
{
    action(frog);
}
...
Action<Animal> feed = animal=>{animal.Feed();}
DoSomethingToAFrog(feed, new Frog());

Action<T>デリゲートは反変です。カエルが動物に変換可能であるという事実は、Action<Animal>Action<Frog>に変換可能であることを意味します。この関係が共変量の反対方向であることに注意してください。それが「コントラ」バリアントである理由です。兌換性があるため、このコードは成功します。 C#3では失敗していたでしょう。

これは理にかなっているはずです。アクションはどの動物でも取ることができます。どんなカエルも捕まえることができる行動が必要であり、どんな動物も確実に捕まえることができる行動はどんなカエルもとることができます。

不変性の例:

void ReadAndWrite(IList<Mammal> mammals)
{
    Mammal mammal = mammals[0];
    mammals[0] = new Tiger();
}

このことにIList<Giraffe>を渡すことはできますか?いいえ、誰かがそれにタイガーを書くつもりであり、タイガーはキリンのリストに入れることができないからです。このことにIList<Animal>を渡すことはできますか?いいえ、哺乳類を読み上げる予定であり、動物のリストにカエルが含まれている可能性があるためです。 IList<T>不変です。それは実際のものとしてのみ使用することができます。

この機能の設計に関するその他の考えについては、設計と構築の方法に関する一連の記事を参照してください。

http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/

89
Eric Lippert

不変性(このコンテキストでは)は、共分散と逆分散の両方がないことです。したがって、Wordcontra-invarianceは意味がありません。 inまたはoutのいずれかとしてタグ付けされていない型パラメーターは不変です。これは、このタイプのパラメーターを消費して返すことができることを意味します。

共分散の良い例はIEnumerable<out T>です。これは、IEnumerable<Derived>IEnumerable<Base>の代わりに使用できるためです。または、タイプTの値を返すFunc<out T>
たとえば、犬は動物であるため、IEnumerable<Dog>IEnumerable<Animal>に変換できます。

逆分散の場合は、消費するインターフェイスまたはデリゲートを使用できます。 IComparer<in T>またはAction<in T>が頭に浮かびます。これらはタイプTの変数を返すことはなく、受け取るだけです。 Baseを受け取る予定の場所ならどこでも、Derivedを渡すことができます。

それらを入力専用または出力専用の型パラメーターと考えると、IMOを理解しやすくなります。

また、Wordinvariantsは通常、型の分散と一緒に使用されるのではなく、クラスまたはメソッドの不変条件のコンテキストで使用され、保存されたプロパティを表します。 このstackoverflowスレッド を参照してください。ここでは、不変条件と不変条件の違いについて説明しています。

4
CodesInChaos

ジェネリックスの定期的な使用を検討する場合、オブジェクトを処理するために定期的にインターフェイスを使用しますが、オブジェクトはクラスのインスタンスであり、インターフェイスをインスタンス化することはできません。例として、文字列の簡単なリストを使用します。

IList<string> strlist = new List<string>();

IList<>を直接使用するのではなく、List<>を使用することの利点をご存知だと思います。これにより、制御の反転が可能になり、List<>を使用したくないが、代わりにLinkedList<>が必要になる場合があります。インターフェイスとクラスの両方のジェネリック型が同じであるため、上記のコードは正常に機能します:string

ただし、文字列のリストのリストを作成する場合は、少し複雑になる可能性があります。この例を考えてみましょう。

IList<IList<string>> strlists = new List<List<string>>();

ジェネリック型の引数IList<string>List<string>は同じではないため、これは明らかにコンパイルされません。 List<IList<string>>のように外部リストを通常のクラスとして宣言した場合でも、コンパイルされません。型引数が一致しません。

したがって、ここで共分散が役立ちます。共分散を使用すると、この式の型引数としてより派生した型を使用できます。 IList<>が共変になるように作成された場合、コンパイルと問題の修正が簡単になります。残念ながら、IList<>は共変ではありませんが、それが拡張するインターフェースの1つは次のとおりです。

IEnumerable<IList<string>> strlists = new List<List<string>>();

このコードはコンパイルされ、型引数は上記と同じです。

2
Mark H