web-dev-qa-db-ja.com

C#の共変および反変のインターフェイスについて

私はC#で読んでいる教科書でこれらに出くわしましたが、おそらく文脈の欠如のために、それらを理解するのが困難です。

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

明確にするために編集します。

共変インターフェイス:

interface IBibble<out T>
.
.

反変インターフェイス:

interface IBibble<in T>
.
.
81
NibblyPig

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

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

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

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

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

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

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

この理由は、コンパイラがあなたが何を意図しているかを「知らない」、またはcan、あなたがそれを取得した後にanimalsコレクションを処理するからです。 。知っている限りでは、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>を返しますが、コンパイラcouldはこのコードを許可します。タイプをこれに変更しました:

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のinキーワードとoutキーワードは、インターフェイスをどちらか一方として明確にマークします。 inを使用すると、ジェネリック型(通常T)をinput-positionsに配置できます。これはメソッド引数を意味し、書き込み専用プロパティ。

outを使用すると、ジェネリック型をoutput-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

この投稿 は、このテーマについて読んだ中で最高です

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

6
Ben G