私は C# で読んでいる教科書でこれらに出くわしましたが、おそらくコンテキストが不足しているため、理解するのが困難です。
それらが何であり、何に役立つかについての簡潔な説明はありますか?
明確にするために編集:
共変インターフェース:
interface IBibble<out T>
.
.
反変インターフェース:
interface IBibble<in T>
.
.
私は 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];
リストは実際には動物のリストであるため、これは許可されていません。
したがって、反分散と共分散は、オブジェクト参照をどのように処理し、それらに対して何ができるかということです。
C# 4.0のin
andout
キーワードは、具体的にインターフェイスをどちらかとしてマークします。では、メソッド引数と書き込み専用プロパティを意味する入力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 にイン方向とアウト方向の両方があるため、共変でも反変でもありませんが、次のようにオブジェクトを追加できるインターフェイスです。
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
この投稿は、この件に関して私が読んだ中で最高のものです
要するに、共変性/反変性/不変性は、自動型キャスト (ベースから派生へ、およびその逆) を扱います。これらの型キャストは、キャストされたオブジェクトで実行される読み取り/書き込みアクションに関していくつかの保証が尊重される場合にのみ可能です。詳細については投稿をお読みください。