1. ホーム
  2. c#

[解決済み] C#の共変量インターフェースと共変量インターフェースの理解

2023-04-10 03:37:29

質問

C#の教科書を読んでいて、このような記述を目にしたのですが、文脈が分からないためか、なかなか理解することができません。

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

分かりやすくするために編集しました。

共変量インターフェイスです。

interface IBibble<out T>
.
.

反変化体インターフェース.

interface IBibble<in T>
.
.

どのように解決するのですか?

とは <out T> とすることで、インターフェース参照を1つ上の階層として扱うことができます。

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

もう少し英語で説明しましょう。

動物園から動物のリストを取得し、それを処理するつもりだとします。(動物園の) すべての動物には名前と一意の 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];

リストは実際には動物のリストであるため、これは許されない。

つまり、contra-とco-varianceは、オブジェクト参照をどう扱うか、何をすることが許されるかということですね。

inout のキーワードは、特にインターフェイスをどちらか一方にマークします。このキーワードは in では、一般的な型(通常はT)を 入力 -の位置、つまりメソッドの引数や書き込み専用のプロパティに置くことができます。

とは 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にインとアウトの両方があるので、co-variantでもcontra-variantでもなく、このようにオブジェクトを追加することができるインタフェースでした。

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