C#の共変および反変インターフェースを理解する


86

C#で読んでいる教科書でこれらに出くわしましたが、おそらく文脈が不足しているために、理解するのに苦労しています。

それらが何であるか、そしてそれらがそこに何のために役立つかについての良い簡潔な説明はありますか?

明確にするために編集:

共変インターフェース:

interface IBibble<out T>
.
.

反変インターフェース:

interface IBibble<in T>
.
.

3
これは短いといいexpalnationの私見です:blogs.msdn.com/csharpfaq/archive/2010/02/16/...
digEmAll

役に立つかもしれません:ブログ投稿
Krunal 2010

うーん、それは良いことですが、それが私を本当に困惑させている理由を説明していません。
nibblyPig 2010

回答:


144

を使用<out T>すると、インターフェイス参照を階層の上位にあるものとして扱うことができます。

<in T>すると、インターフェイス参照を階層内で下向きのものとして扱うことができます。

もっと英語で説明してみましょう。

動物園から動物のリストを取得していて、それらを処理するつもりであるとしましょう。(動物園内の)すべての動物には、名前と一意のIDがあります。一部の動物は哺乳類、一部は爬虫類、一部は両生類、一部は魚などですが、すべて動物です。

したがって、動物のリスト(さまざまな種類の動物が含まれています)を使用すると、すべての動物に名前があると言えます。したがって、すべての動物の名前を取得しても安全であることは明らかです。

ただし、魚のリストだけがあり、それらを動物のように扱う必要がある場合はどうなりますか?直感的には機能するはずですが、C#3.0以前では、このコードはコンパイルされません。

IEnumerable<Animal> animals = GetFishes(); // returns IEnumerable<Fish>

これは、コンパイラが、動物コレクションを取得した後、そのコレクションで何を意図しているか、または何ができるかを「認識」していないためです。知っている限りでIEnumerable<T>は、オブジェクトをリストに戻す方法がある可能性があります。これにより、魚ではない動物を、魚のみを含むはずのコレクションに入れることができる可能性があります。

言い換えると、コンパイラーはこれが許可されないことを保証できません。

animals.Add(new Mammal("Zebra"));

したがって、コンパイラはコードのコンパイルを完全に拒否します。これは共分散です。

共変性を見てみましょう。

私たちの動物園はすべての動物を扱うことができるので、確かに魚を扱うことができるので、私たちの動物園にいくつかの魚を追加してみましょう。

C#3.0以前では、これはコンパイルされません。

List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>
fishes.Add(new Fish("Guppy"));

ここでは、すべての魚が動物であるという理由だけでメソッドが返される場合でも、コンパイラこのコードを許可する可能List<Animal>性があるため、タイプを次のように変更した場合は次のようになります。

List<Animal> fishes = GetAccessToFishes();
fishes.Add(new Fish("Guppy"));

その後は機能しますが、コンパイラは、これを実行しようとしていないことを判別できません。

List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>
Fish firstFist = fishes[0];

リストは実際には動物のリストであるため、これは許可されていません。

したがって、逆分散と共分散は、オブジェクト参照の処理方法と、それらを使用して実行できることです。

C#4.0のinandoutキーワードは、インターフェイスをどちらか一方として明確にマークします。を使用するinと、ジェネリック型(通常はT)を入力位置に配置できます。これはメソッド引数と書き込み専用プロパティを意味します。

を使用するoutと、ジェネリック型を出力に配置できます -positionsこれは、メソッドの戻り値、読み取り専用プロパティ、およびoutメソッドのパラメーターです。

これにより、コードで意図したことを実行できるようになります。

IEnumerable<Animal> animals = GetFishes(); // returns IEnumerable<Fish>
// since we can only get animals *out* of the collection, every fish is an animal
// so this is safe

List<T> Tには内方向と外方向の両方があるため、共変でも反変でもありませんが、次のようなオブジェクトを追加できるインターフェイスです。

interface IWriteOnlyList<in T>
{
    void Add(T value);
}

これを行うことができます:

IWriteOnlyList<Fish> fishes = GetWriteAccessToAnimals(); // still returns
                                                            IWriteOnlyList<Animal>
fishes.Add(new Fish("Guppy")); <-- this is now safe

コンセプトを示すいくつかのビデオがあります:

次に例を示します。

namespace SO2719954
{
    class Base { }
    class Descendant : Base { }

    interface IBibbleOut<out T> { }
    interface IBibbleIn<in T> { }

    class Program
    {
        static void Main(string[] args)
        {
            // We can do this since every Descendant is also a Base
            // and there is no chance we can put Base objects into
            // the returned object, since T is "out"
            // We can not, however, put Base objects into b, since all
            // Base objects might not be Descendant.
            IBibbleOut<Base> b = GetOutDescendant();

            // We can do this since every Descendant is also a Base
            // and we can now put Descendant objects into Base
            // We can not, however, retrieve Descendant objects out
            // of d, since all Base objects might not be Descendant
            IBibbleIn<Descendant> d = GetInBase();
        }

        static IBibbleOut<Descendant> GetOutDescendant()
        {
            return null;
        }

        static IBibbleIn<Base> GetInBase()
        {
            return null;
        }
    }
}

これらのマークがないと、以下がコンパイルされる可能性があります。

public List<Descendant> GetDescendants() ...
List<Base> bases = GetDescendants();
bases.Add(new Base()); <-- uh-oh, we try to add a Base to a Descendant

またはこれ:

public List<Base> GetBases() ...
List<Descendant> descendants = GetBases(); <-- uh-oh, we try to treat all Bases
                                               as Descendants

うーん、共分散と反変性の目標を説明できますか?それをもっと理解するのに役立つかもしれません。
nibblyPig 2010

1
最後のビットを参照してください。これは、コンパイラーが以前に防止したことです。inおよびoutの目的は、コンパイラーが安全なことを実行することを妨げないように、安全なインターフェース(またはタイプ)で何ができるかを言うことです。 。
Lasse V. Karlsen 2010

すばらしい答えです。私はそれらが非常に役立つビデオを見て、あなたの例と組み合わせて、今それを分類しました。残っている質問は1つだけです。それが、「out」と「in」が必要な理由です。VisualStudioが、何をしようとしているのか(またはその背後にある理由を)自動的に認識しないのはなぜですか?
nibblyPig 2010

Automagicの「そこで何をしようとしているのかわかります」は、クラスのようなものを宣言することになると通常は嫌われます。プログラマーに型を明示的にマークさせる方がよいでしょう。Tを返すメソッドを持つクラスに「in」を追加してみると、コンパイラは文句を言います。以前に自動的に追加された「in」をサイレントに削除するとどうなるか想像してみてください。
Lasse V. Karlsen 2010

1
1つのキーワードでこの長い説明が必要な場合は、明らかに問題があります。私の意見では、C#はこの特定のケースではあまりにも賢くしようとしています。それでも、かっこいい説明ありがとうございます。
rr- 2014年

7

この郵便受けは私がこのテーマについて読んだ中で最高です

要するに、共変性/反変性/不変性は、自動型キャスト(ベースから派生へ、またはその逆)を扱います。これらの型キャストは、キャストされたオブジェクトに対して実行される読み取り/書き込みアクションに関していくつかの保証が尊重されている場合にのみ可能です。詳細については、投稿をお読みください。


5
リンクが切れているようです。ここでアーカイブされたバージョンです:web.archive.org/web/20140626123445/http://adamnathan.co.uk/...
si618は

1
私のようなこの説明さらに:codepureandsimple.com/...
volkit
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.