C#で読んでいる教科書でこれらに出くわしましたが、おそらく文脈が不足しているために、理解するのに苦労しています。
それらが何であるか、そしてそれらがそこに何のために役立つかについての良い簡潔な説明はありますか?
明確にするために編集:
共変インターフェース:
interface IBibble<out T>
.
.
反変インターフェース:
interface IBibble<in T>
.
.
C#で読んでいる教科書でこれらに出くわしましたが、おそらく文脈が不足しているために、理解するのに苦労しています。
それらが何であるか、そしてそれらがそこに何のために役立つかについての良い簡潔な説明はありますか?
明確にするために編集:
共変インターフェース:
interface IBibble<out T>
.
.
反変インターフェース:
interface IBibble<in T>
.
.
回答:
を使用<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のin
andout
キーワードは、インターフェイスをどちらか一方として明確にマークします。を使用する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
この郵便受けは私がこのテーマについて読んだ中で最高です
要するに、共変性/反変性/不変性は、自動型キャスト(ベースから派生へ、またはその逆)を扱います。これらの型キャストは、キャストされたオブジェクトに対して実行される読み取り/書き込みアクションに関していくつかの保証が尊重されている場合にのみ可能です。詳細については、投稿をお読みください。