わかりました、stackoverflowでこのトピックについて少し読んで、 this & this を見ましたが、それでもco/contra-varianceについて少し混乱しています。
から ここ
共分散により、元の型が「出力」位置でのみ使用される(たとえば、戻り値として)APIで「より大きな」(特定性の低い)型を置き換えることができます。共変性により、元のタイプが「入力」位置でのみ使用されるAPIで、「より小さな」(より具体的な)タイプを置き換えることができます。
私はそれが型安全性と関係があることを知っています。
_in/out
_のことについて。書き込む必要がある場合はin
を使用し、読み取り専用の場合はout
を使用すると言えますか。 in
は逆分散、out
共分散を意味します。しかし、上記の説明から...
および ここ
たとえば、
list.Add(new Apple())
はListには有効ですが、_List<Banana>
_には有効でないため、_List<Fruit>
_を_List<Banana>
_として扱うことはできません。
in
/を使用してオブジェクトに書き込む場合は、より大きく、より一般的である必要があります。
私はこの質問がされたことを知っていますが、それでも非常に混乱しています。
C#4.0の共変性と反変性はどちらも、基本クラスの代わりに派生クラスを使用する機能を指します。 in/outキーワードは、タイプパラメータが入力と出力に使用されるかどうかを示すコンパイラのヒントです。
C#4.0の共分散は、out
キーワードによって支援されます。これは、out
型パラメーターの派生クラスを使用するジェネリック型がOKであることを意味します。したがって、
IEnumerable<Fruit> fruit = new List<Apple>();
Apple
はFruit
であるため、List<Apple>
はIEnumerable<Fruit>
として安全に使用できます。
共変性はin
キーワードであり、通常はデリゲートでの入力タイプを示します。原則は同じです。つまり、デリゲートはより多くの派生クラスを受け入れることができます。
public delegate void Func<in T>(T param);
これは、Func<Fruit>
がある場合、それをFunc<Apple>
に変換できることを意味します。
Func<Fruit> fruitFunc = (fruit)=>{};
Func<Apple> appleFunc = fruitFunc;
原理は同じですが、派生型からベースへの安全なキャストであるため、入力型で使用すると、派生型の少ない型(Func<Fruit>
)を派生型の型(Func<Apple>
)に安全にキャストできます。 、これは理にかなっています。Fruit
を取る関数は、Apple
を取ることもできるからです。
私はこれをうまく説明する方法について長くそして一生懸命考えなければなりませんでした。説明するのは理解するのと同じくらい難しいようです。
基本クラスのFruitがあると想像してください。そして、2つのサブクラスAppleとバナナがあります。
Fruit
/ \
Banana Apple
2つのオブジェクトを作成します。
Apple a = new Apple();
Banana b = new Banana();
これらのオブジェクトの両方について、Fruitオブジェクトにタイプキャストできます。
Fruit f = (Fruit)a;
Fruit g = (Fruit)b;
派生クラスは、基本クラスであるかのように扱うことができます。
ただし、基本クラスを派生クラスのように扱うことはできません。
a = (Apple)f; //This is incorrect
これをリストの例に適用してみましょう。
2つのリストを作成したとします。
List<Fruit> fruitList = new List<Fruit>();
List<Banana> bananaList = new List<Banana>();
あなたはこのようなことをすることができます...
fruitList.Add(new Apple());
そして
fruitList.Add(new Banana());
これは、リストに追加するときに基本的に型キャストするためです。あなたはそれをこのように考えることができます...
fruitList.Add((Fruit)new Apple());
fruitList.Add((Fruit)new Banana());
ただし、逆の場合に同じロジックを適用すると、いくつかの危険信号が発生します。
bananaList.Add(new Fruit());
と同じです
bannanaList.Add((Banana)new Fruit());
基本クラスを派生クラスのように扱うことはできないため、これによりエラーが発生します。
なぜこれがエラーを引き起こすのかという質問があった場合に備えて、それについても説明します。
これがフルーツクラスです
public class Fruit
{
public Fruit()
{
a = 0;
}
public int A { get { return a; } set { a = value } }
private int a;
}
これがバナナのクラスです
public class Banana: Fruit
{
public Banana(): Fruit() // This calls the Fruit constructor
{
// By calling ^^^ Fruit() the inherited variable a is also = 0;
b = 0;
}
public int B { get { return b; } set { b = value; } }
private int b;
}
もう一度2つのオブジェクトを作成したと想像してください
Fruit f = new Fruit();
Banana ba = new Banana();
バナナには2つの変数「a」と「b」がありますが、フルーツには1つの「a」しかないことに注意してください。だからあなたがこれをするとき...
f = (Fruit)b;
f.A = 5;
完全なFruitオブジェクトを作成します。しかし、もしあなたがこれをするなら...
ba = (Banana)f;
ba.A = 5;
ba.B = 3; //Error!!!: Was "b" ever initialized? Does it exist?
問題は、完全なBananaクラスを作成していないことです。すべてのデータメンバーが宣言/初期化されているわけではありません。
今、私はシャワーから戻って、少し複雑になるここで自分自身に軽食を取りました。
後から考えると、複雑なものに入るときに比喩を落とすべきでした
2つの新しいクラスを作成しましょう:
public class Base
public class Derived : Base
彼らはあなたが好きなことをすることができます
次に、2つの関数を定義しましょう
public Base DoSomething(int variable)
{
return (Base)DoSomethingElse(variable);
}
public Derived DoSomethingElse(int variable)
{
// Do stuff
}
これは、「out」がどのように機能するかのようなもので、派生クラスを基本クラスであるかのように常に使用できるはずです。これをインターフェイスに適用しましょう。
interface MyInterface<T>
{
T MyFunction(int variable);
}
Out/inの主な違いは、Genericが戻り値の型またはメソッドパラメータとして使用される場合です。これは前者の場合です。
このインターフェースを実装するクラスを定義しましょう:
public class Thing<T>: MyInterface<T> { }
次に、2つのオブジェクトを作成します。
MyInterface<Base> base = new Thing<Base>;
MyInterface<Derived> derived = new Thing<Derived>;
あなたがこれをした場合:
base = derived;
「暗黙的に...から変換できません」のようなエラーが発生します。
2つの選択肢があります。1)明示的に変換するか、2)コンパイラに暗黙的に変換するように指示します。
base = (MyInterface<Base>)derived; // #1
または
interface MyInterface<out T> // #2
{
T MyFunction(int variable);
}
2番目のケースは、インターフェイスが次のようになっている場合に発生します。
interface MyInterface<T>
{
int MyFunction(T variable); // T is now a parameter
}
それを再び2つの機能に関連付ける
public int DoSomething(Base variable)
{
// Do stuff
}
public int DoSomethingElse(Derived variable)
{
return DoSomething((Base)variable);
}
うまくいけば、状況がどのように逆転したかがわかりますが、本質的に同じタイプの変換です。
同じクラスを再度使用する
public class Base
public class Derived : Base
public class Thing<T>: MyInterface<T> { }
と同じオブジェクト
MyInterface<Base> base = new Thing<Base>;
MyInterface<Derived> derived = new Thing<Derived>;
それらを等しく設定しようとすると
base = derived;
あなたのコンパイラーは再びあなたに怒鳴ります、あなたは以前と同じオプションを持っています
base = (MyInterface<Base>)derived;
または
interface MyInterface<in T> //changed
{
int MyFunction(T variable); // T is still a parameter
}
基本的に、ジェネリックがインターフェイスメソッドの戻り値の型としてのみ使用される場合に使用します。メソッドパラメータとして使用する場合に使用します。デリゲートを使用する場合にも同じルールが適用されます。
奇妙な例外がありますが、ここではそれらについて心配するつもりはありません。
事前に不注意な間違いをしてすみません=)
このトピックについての私の見解を共有させてください。
_class Animal { }
class Mammal : Animal { }
class Dog : Mammal { }
_
in
およびout
汎用修飾子が実際に何をするかを説明します。_interface IInvariant<T>
{
T Get(); // ok, an invariant type can be both put into and returned
void Set(T t); // ok, an invariant type can be both put into and returned
}
interface IContravariant<in T>
{
//T Get(); // compilation error, cannot return a contravariant type
void Set(T t); // ok, a contravariant type can only be **put into** our class (hence "in")
}
interface ICovariant<out T>
{
T Get(); // ok, a covariant type can only be **returned** from our class (hence "out")
//void Set(T t); // compilation error, cannot put a covariant type into our class
}
_
では、なぜin
およびout
修飾子を使用するインターフェースをわざわざ使用するのですか?restrict us?どれどれ:
不変性から始めましょう(in
なし、out
修飾子なし)
_IInvariant<Mammal>
_を検討してください
IInvariant<Mammal>.Get()
-哺乳類を返しますIInvariant<Mammal>.Set(Mammal)
-哺乳類を受け入れます試してみるとどうなりますか:IInvariant<Mammal> invariantMammal = (IInvariant<Animal>)null
?
IInvariant<Mammal>.Get()
を呼び出す人は誰でも哺乳類を期待しますが、IInvariant<Animal>.Get()
-は動物を返します。すべての動物が哺乳類であるとは限らないので、互換性がないです。IInvariant<Mammal>.Set(Mammal)
を呼び出す人は誰でも、哺乳類が渡されることを期待しています。 IInvariant<Animal>.Set(Animal)
はany動物(哺乳類を含む)を受け入れるので、互換性そして、試してみるとどうなりますか:IInvariant<Mammal> invariantMammal = (IInvariant<Dog>)null
?
IInvariant<Mammal>.Get()
を呼び出す人は誰でも哺乳類を期待し、IInvariant<Dog>.Get()
-はDogを返します。すべての犬は哺乳類なので、互換性です。IInvariant<Mammal>.Set(Mammal)
を呼び出す人は誰でも、哺乳類が渡されることを期待しています。 IInvariant<Dog>.Set(Dog)
はのみ犬を受け入れるので(そしてすべての哺乳類を犬としてではない)、それは互換性がないです。私たちが正しいかどうかを確認しましょう
_IInvariant<Animal> invariantAnimal1 = (IInvariant<Animal>)null; // ok
IInvariant<Animal> invariantAnimal2 = (IInvariant<Mammal>)null; // compilation error
IInvariant<Animal> invariantAnimal3 = (IInvariant<Dog>)null; // compilation error
IInvariant<Mammal> invariantMammal1 = (IInvariant<Animal>)null; // compilation error
IInvariant<Mammal> invariantMammal2 = (IInvariant<Mammal>)null; // ok
IInvariant<Mammal> invariantMammal3 = (IInvariant<Dog>)null; // compilation error
IInvariant<Dog> invariantDog1 = (IInvariant<Animal>)null; // compilation error
IInvariant<Dog> invariantDog2 = (IInvariant<Mammal>)null; // compilation error
IInvariant<Dog> invariantDog3 = (IInvariant<Dog>)null; // ok
_
これ1つIS重要:ジェネリック型パラメーターがクラス階層で上位か下位かによって、ジェネリック型自体が互換性がない)であることに注意してください。さまざまな理由で。
では、どうすればそれを悪用できるかを調べてみましょう。
out
)out
汎用修飾子を使用すると共分散があります(上記を参照)
タイプが_ICovariant<Mammal>
_のようになっている場合、次の2つのことを宣言します。
out
汎用修飾子)-これは退屈ですout
汎用修飾子であるため、これは興味深いですout
修飾子の制限からどのように利益を得ることができますか?上記の「不変性実験」の結果を振り返ってください。共分散について同じ実験を行うとどうなるか見てみましょう。
試してみるとどうなりますか:ICovariant<Mammal> covariantMammal = (ICovariant<Animal>)null
?
ICovariant<Mammal>.Get()
を呼び出す人は誰でも哺乳類を期待しますが、ICovariant<Animal>.Get()
-は動物を返します。すべての動物が哺乳類であるとは限らないので、互換性がないです。out
修飾子の制限により、これは問題ではなくなりました。そして、試してみるとどうなりますか:ICovariant<Mammal> covariantMammal = (ICovariant<Dog>)null
?
ICovariant<Mammal>.Get()
を呼び出す人は誰でも哺乳類を期待し、ICovariant<Dog>.Get()
-はDogを返します。すべての犬は哺乳類なので、互換性です。out
修飾子の制限により、これは問題ではなくなりました。コードで確認しましょう:
_ICovariant<Animal> covariantAnimal1 = (ICovariant<Animal>)null; // ok
ICovariant<Animal> covariantAnimal2 = (ICovariant<Mammal>)null; // ok!!!
ICovariant<Animal> covariantAnimal3 = (ICovariant<Dog>)null; // ok!!!
ICovariant<Mammal> covariantMammal1 = (ICovariant<Animal>)null; // compilation error
ICovariant<Mammal> covariantMammal2 = (ICovariant<Mammal>)null; // ok
ICovariant<Mammal> covariantMammal3 = (ICovariant<Dog>)null; // ok!!!
ICovariant<Dog> covariantDog1 = (ICovariant<Animal>)null; // compilation error
ICovariant<Dog> covariantDog2 = (ICovariant<Mammal>)null; // compilation error
ICovariant<Dog> covariantDog3 = (ICovariant<Dog>)null; // ok
_
in
)in
汎用修飾子を使用すると、反変性が発生します(上記を参照)
タイプが_IContravariant<Mammal>
_のようになっている場合、次の2つのことを宣言します。
in
汎用修飾子)-これは退屈ですin
ジェネリック修飾子によって課せられるので興味深いです試してみるとどうなりますか:IContravariant<Mammal> contravariantMammal = (IContravariant<Animal>)null
?
IContravariant<Mammal>.Get()
in
修飾子の制限により、これは問題ではなくなりました。IContravariant<Mammal>.Set(Mammal)
を呼び出す人は誰でも、哺乳類が渡されることを期待しています。 IContravariant<Animal>.Set(Animal)
はany動物(哺乳類を含む)を受け入れるので、互換性そして、試してみるとどうなりますか:IContravariant<Mammal> contravariantMammal = (IContravariant<Dog>)null
?
IContravariant<Mammal>.Get()
in
修飾子の制限により、これは問題ではなくなりました。IContravariant<Mammal>.Set(Mammal)
を呼び出す人は誰でも、哺乳類が渡されることを期待しています。 IContravariant<Dog>.Set(Dog)
はのみ犬を受け入れるので(そしてすべての哺乳類を犬としてではない)、それは互換性がないです。コードで確認しましょう:
_IContravariant<Animal> contravariantAnimal1 = (IContravariant<Animal>)null; // ok
IContravariant<Animal> contravariantAnimal2 = (IContravariant<Mammal>)null; // compilation error
IContravariant<Animal> contravariantAnimal3 = (IContravariant<Dog>)null; // compilation error
IContravariant<Mammal> contravariantMammal1 = (IContravariant<Animal>)null; // ok!!!
IContravariant<Mammal> contravariantMammal2 = (IContravariant<Mammal>)null; // ok
IContravariant<Mammal> contravariantMammal3 = (IContravariant<Dog>)null; // compilation error
IContravariant<Dog> contravariantDog1 = (IContravariant<Animal>)null; // ok!!!
IContravariant<Dog> contravariantDog2 = (IContravariant<Mammal>)null; // ok!!!
IContravariant<Dog> contravariantDog3 = (IContravariant<Dog>)null; // ok
_
ところで、これは少し直感に反しているように感じますね。
_// obvious
Animal animal = (Dog)null; // ok
Dog dog = (Animal)null; // compilation error, not every Animal is a Dog
// but this looks like the other way around
IContravariant<Animal> contravariantAnimal = (IContravariant<Dog>) null; // compilation error
IContravariant<Dog> contravariantDog = (IContravariant<Animal>) null; // ok
_
では、in
とout
の両方の汎用修飾子を使用できますか? -明らかにない。
どうして? in
およびout
修飾子が課す制限を振り返ってください。ジェネリック型パラメーターを共変と反変の両方にしたい場合は、基本的に次のように言います。
T
を返しませんT
を受け入れませんこれは本質的に私たちのジェネリックインターフェースを非ジェネリックにします。
あなたは私のトリックを使うことができます:)
共分散は非常に理解しやすいです。それは当然です。共変性はもっと紛らわしいです。
これをよく見てください MSDNの例 。 SortedListがIComparerをどのように期待するかを確認しますが、ShapeAreaComparer:IComparerを渡します。 Shapeは「大きい」タイプ(呼び出し元ではなく呼び出し先の署名にあります)ですが、共変性により、通常はShapeを使用するShapeAreaComparerのすべての場所を「小さい」タイプ(Circle)に置き換えることができます。
お役に立てば幸いです。
ジョンズの言葉で:
共分散により、元の型が「出力」位置でのみ使用される(たとえば、戻り値として)APIで「より大きな」(あまり具体的でない)型置換されるが可能になります。共変性により、元の型が「入力」位置でのみ使用されるAPIで「より小さな」(より具体的な)型置換されるが可能になります。
彼の説明は最初は紛らわしいと思いましたが、C#プログラミングガイドの例と組み合わせて、置き換えられることが強調されていることは一度私には理にかなっています。
// Covariance.
IEnumerable<string> strings = new List<string>();
// An object that is instantiated with a more derived type argument
// is assigned to an object instantiated with a less derived type argument.
// Assignment compatibility is preserved.
IEnumerable<object> objects = strings;
// Contravariance.
// Assume that the following method is in the class:
// static void SetObject(object o) { }
Action<object> actObject = SetObject;
// An object that is instantiated with a less derived type argument
// is assigned to an object instantiated with a more derived type argument.
// Assignment compatibility is reversed.
Action<string> actString = actObject;
コンバーターデリゲートは私がそれを理解するのを助けます:
delegate TOutput Converter<in TInput, out TOutput>(TInput input);
TOutput
は共分散を表します。メソッドはより具体的なタイプを返します。
TInput
はcontravarianceを表します。ここでメソッドはより少ない特定のタイプに渡されます。
public class Dog { public string Name { get; set; } }
public class Poodle : Dog { public void DoBackflip(){ System.Console.WriteLine("2nd smartest breed - woof!"); } }
public static Poodle ConvertDogToPoodle(Dog dog)
{
return new Poodle() { Name = dog.Name };
}
List<Dog> dogs = new List<Dog>() { new Dog { Name = "Truffles" }, new Dog { Name = "Fuzzball" } };
List<Poodle> poodles = dogs.ConvertAll(new Converter<Dog, Poodle>(ConvertDogToPoodle));
poodles[0].DoBackflip();
トピックに入る前に、簡単に復習しましょう。
基本クラス参照は派生クラスオブジェクトを保持できますが、その逆はできません。
Covariance:Covarianceを使用すると、基本型オブジェクトが期待される派生型オブジェクトを渡すことができます。Covarianceは、デリゲート、ジェネリック、配列、インターフェイスなどに適用できます。
Contravariance: Contravarianceはパラメーターに適用されます。これにより、基本クラスのパラメーターを持つメソッドを、派生クラスのパラメーターを期待するデリゲートに割り当てることができます。
以下の簡単な例を見てください。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CovarianceContravarianceDemo
{
//base class
class A
{
}
//derived class
class B : A
{
}
class Program
{
static A Method1(A a)
{
Console.WriteLine("Method1");
return new A();
}
static A Method2(B b)
{
Console.WriteLine("Method2");
return new A();
}
static B Method3(B b)
{
Console.WriteLine("Method3");
return new B();
}
public delegate A MyDelegate(B b);
static void Main(string[] args)
{
MyDelegate myDel = null;
myDel = Method2;// normal assignment as per parameter and return type
//Covariance, delegate expects a return type of base class
//but we can still assign Method3 that returns derived type and
//Thus, covariance allows you to assign a method to the delegate that has a less derived return type.
myDel = Method3;
A a = myDel(new B());//this will return a more derived type object which can be assigned to base class reference
//Contravariane is applied to parameters.
//Contravariance allows a method with the parameter of a base class to be assigned to a delegate that expects the parameter of a derived class.
myDel = Method1;
myDel(new B()); //Contravariance,
}
}
}