現実の世界で共分散と反変をどのように使用するかを理解するのに少し問題があります。
これまでのところ、私が見た唯一の例は、同じ古い配列の例です。
object[] objectArray = new string[] { "string 1", "string 2" };
それが他の場所で使用されているのを見ることができれば、開発中にそれを使用できるようにする例を見るとよいでしょう。
現実の世界で共分散と反変をどのように使用するかを理解するのに少し問題があります。
これまでのところ、私が見た唯一の例は、同じ古い配列の例です。
object[] objectArray = new string[] { "string 1", "string 2" };
それが他の場所で使用されているのを見ることができれば、開発中にそれを使用できるようにする例を見るとよいでしょう。
回答:
クラスPersonと、それから派生するクラスTeacherがあるとします。IEnumerable<Person>
引数として取る操作がいくつかあります。Schoolクラスには、を返すメソッドがありますIEnumerable<Teacher>
。共分散を使用すると、その結果を、を使用するメソッドに直接使用することができIEnumerable<Person>
、より派生型の少ない(より一般的な)型をより派生型に置き換えることができます。逆直観では、直観的には、より派生的な型が指定されているより一般的な型を使用できます。
MSDNのGenericsのCovarianceおよびContravarianceも参照してください。
クラス:
public class Person
{
public string Name { get; set; }
}
public class Teacher : Person { }
public class MailingList
{
public void Add(IEnumerable<out Person> people) { ... }
}
public class School
{
public IEnumerable<Teacher> GetTeachers() { ... }
}
public class PersonNameComparer : IComparer<Person>
{
public int Compare(Person a, Person b)
{
if (a == null) return b == null ? 0 : -1;
return b == null ? 1 : Compare(a,b);
}
private int Compare(string a, string b)
{
if (a == null) return b == null ? 0 : -1;
return b == null ? 1 : a.CompareTo(b);
}
}
使用法:
var teachers = school.GetTeachers();
var mailingList = new MailingList();
// Add() is covariant, we can use a more derived type
mailingList.Add(teachers);
// the Set<T> constructor uses a contravariant interface, IComparer<in T>,
// we can use a more generic type than required.
// See https://msdn.microsoft.com/en-us/library/8ehhxeaf.aspx for declaration syntax
var teacherSet = new SortedSet<Teachers>(teachers, new PersonNameComparer());
// Contravariance
interface IGobbler<in T> {
void gobble(T t);
}
// Since a QuadrupedGobbler can gobble any four-footed
// creature, it is OK to treat it as a donkey gobbler.
IGobbler<Donkey> dg = new QuadrupedGobbler();
dg.gobble(MyDonkey());
// Covariance
interface ISpewer<out T> {
T spew();
}
// A MouseSpewer obviously spews rodents (all mice are
// rodents), so we can treat it as a rodent spewer.
ISpewer<Rodent> rs = new MouseSpewer();
Rodent r = rs.spew();
完全を期して…
// Invariance
interface IHat<T> {
void hide(T t);
T pull();
}
// A RabbitHat…
IHat<Rabbit> rHat = RabbitHat();
// …cannot be treated covariantly as a mammal hat…
IHat<Mammal> mHat = rHat; // Compiler error
// …because…
mHat.hide(new Dolphin()); // Hide a dolphin in a rabbit hat??
// It also cannot be treated contravariantly as a cottontail hat…
IHat<CottonTail> cHat = rHat; // Compiler error
// …because…
rHat.hide(new MarshRabbit());
cHat.pull(); // Pull a marsh rabbit out of a cottontail hat??
void feed(IGobbler<Donkey> dg)
。代わりにIGobbler <Quadruped>をパラメーターとして受け取った場合、ロバのみを食べるドラゴンを渡すことができません。
違いを理解するのに役立つようにまとめました
public interface ICovariant<out T> { }
public interface IContravariant<in T> { }
public class Covariant<T> : ICovariant<T> { }
public class Contravariant<T> : IContravariant<T> { }
public class Fruit { }
public class Apple : Fruit { }
public class TheInsAndOuts
{
public void Covariance()
{
ICovariant<Fruit> fruit = new Covariant<Fruit>();
ICovariant<Apple> apple = new Covariant<Apple>();
Covariant(fruit);
Covariant(apple); //apple is being upcasted to fruit, without the out keyword this will not compile
}
public void Contravariance()
{
IContravariant<Fruit> fruit = new Contravariant<Fruit>();
IContravariant<Apple> apple = new Contravariant<Apple>();
Contravariant(fruit); //fruit is being downcasted to apple, without the in keyword this will not compile
Contravariant(apple);
}
public void Covariant(ICovariant<Fruit> fruit) { }
public void Contravariant(IContravariant<Apple> apple) { }
}
tldr
ICovariant<Fruit> apple = new Covariant<Apple>(); //because it's covariant
IContravariant<Apple> fruit = new Contravariant<Fruit>(); //because it's contravariant
Contravariance
場合、果物はどのようにして(例では)リンゴにダウンキャストできますか?Fruit
Apple
inキーワードとoutキーワードは、インターフェイスおよびデリゲートのコンパイラのキャストルールをジェネリックパラメーターで制御します。
interface IInvariant<T> {
// This interface can not be implicitly cast AT ALL
// Used for non-readonly collections
IList<T> GetList { get; }
// Used when T is used as both argument *and* return type
T Method(T argument);
}//interface
interface ICovariant<out T> {
// This interface can be implicitly cast to LESS DERIVED (upcasting)
// Used for readonly collections
IEnumerable<T> GetList { get; }
// Used when T is used as return type
T Method();
}//interface
interface IContravariant<in T> {
// This interface can be implicitly cast to MORE DERIVED (downcasting)
// Usually means T is used as argument
void Method(T argument);
}//interface
class Casting {
IInvariant<Animal> invariantAnimal;
ICovariant<Animal> covariantAnimal;
IContravariant<Animal> contravariantAnimal;
IInvariant<Fish> invariantFish;
ICovariant<Fish> covariantFish;
IContravariant<Fish> contravariantFish;
public void Go() {
// NOT ALLOWED invariants do *not* allow implicit casting:
invariantAnimal = invariantFish;
invariantFish = invariantAnimal; // NOT ALLOWED
// ALLOWED covariants *allow* implicit upcasting:
covariantAnimal = covariantFish;
// NOT ALLOWED covariants do *not* allow implicit downcasting:
covariantFish = covariantAnimal;
// NOT ALLOWED contravariants do *not* allow implicit upcasting:
contravariantAnimal = contravariantFish;
// ALLOWED contravariants *allow* implicit downcasting
contravariantFish = contravariantAnimal;
}//method
}//class
// .NET Framework Examples:
public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable { }
public interface IEnumerable<out T> : IEnumerable { }
class Delegates {
// When T is used as both "in" (argument) and "out" (return value)
delegate T Invariant<T>(T argument);
// When T is used as "out" (return value) only
delegate T Covariant<out T>();
// When T is used as "in" (argument) only
delegate void Contravariant<in T>(T argument);
// Confusing
delegate T CovariantBoth<out T>(T argument);
// Confusing
delegate T ContravariantBoth<in T>(T argument);
// From .NET Framework:
public delegate void Action<in T>(T obj);
public delegate TResult Func<in T, out TResult>(T arg);
}//class
以下は、継承階層を使用した簡単な例です。
単純なクラス階層を考えると:
そしてコードでは:
public abstract class LifeForm { }
public abstract class Animal : LifeForm { }
public class Giraffe : Animal { }
public class Zebra : Animal { }
不変性(つまり、ジェネリック型パラメーターまたはキーワードで装飾されていない*)in
out
一見、このような方法
public static void PrintLifeForms(IList<LifeForm> lifeForms)
{
foreach (var lifeForm in lifeForms)
{
Console.WriteLine(lifeForm.GetType().ToString());
}
}
...異種のコレクションを受け入れる必要があります:(それを行います)
var myAnimals = new List<LifeForm>
{
new Giraffe(),
new Zebra()
};
PrintLifeForms(myAnimals); // Giraffe, Zebra
ただし、より派生した型のコレクションを渡すと失敗します。
var myGiraffes = new List<Giraffe>
{
new Giraffe(), // "Jerry"
new Giraffe() // "Melman"
};
PrintLifeForms(myGiraffes); // Compile Error!
cannot convert from 'System.Collections.Generic.List<Giraffe>' to 'System.Collections.Generic.IList<LifeForm>'
どうして?ジェネリックパラメーターIList<LifeForm>
は共変ではない
ため、IList<T>
不変であるためIList<LifeForm>
、パラメーター化された型がT
必要なコレクション(IListを実装する)のみを受け入れますLifeForm
。
のメソッドの実装PrintLifeForms
が悪意のあるものである(ただし、同じメソッドシグネチャを持っている)場合、コンパイラがパスを妨げる理由List<Giraffe>
が明らかになります。
public static void PrintLifeForms(IList<LifeForm> lifeForms)
{
lifeForms.Add(new Zebra());
}
以来IList
許可が追加または要素の除去、の任意のサブクラスは、LifeForm
このようにパラメータを追加することができlifeForms
、そしてメソッドに渡された派生型の任意のコレクションの種類に違反します。(ここでは、悪意のあるメソッドがをに追加しようとZebra
しますvar myGiraffes
)。幸い、コンパイラーはこの危険から私たちを守ってくれます。
共分散(で装飾されたパラメーター化されたタイプのジェネリックout
)
共分散は不変コレクションで広く使用されています(つまり、新しい要素をコレクションに追加またはコレクションから削除できない場合)
上記の例の解決策は、共変なジェネリックコレクションタイプが使用されるようにすることですIEnumerable
(例:として定義IEnumerable<out T>
)。IEnumerable
コレクションに変更するメソッドはありません。また、out
共分散の結果として、サブタイプがのコレクションをLifeForm
メソッドに渡すことができます。
public static void PrintLifeForms(IEnumerable<LifeForm> lifeForms)
{
foreach (var lifeForm in lifeForms)
{
Console.WriteLine(lifeForm.GetType().ToString());
}
}
PrintLifeForms
今で呼び出すことができZebras
、Giraffes
かつ任意のIEnumerable<>
任意のサブクラスのLifeForm
反変(で装飾されたパラメーター化された型を持つジェネリックin
)
反変は、関数がパラメーターとして渡されるときによく使用されます。
以下Action<Zebra>
は、パラメーターとしてを受け取り、Zebraの既知のインスタンスでそれを呼び出す関数の例です。
public void PerformZebraAction(Action<Zebra> zebraAction)
{
var zebra = new Zebra();
zebraAction(zebra);
}
予想通り、これはうまく機能します:
var myAction = new Action<Zebra>(z => Console.WriteLine("I'm a zebra"));
PerformZebraAction(myAction); // I'm a zebra
直感的に、これは失敗します:
var myAction = new Action<Giraffe>(g => Console.WriteLine("I'm a giraffe"));
PerformZebraAction(myAction);
cannot convert from 'System.Action<Giraffe>' to 'System.Action<Zebra>'
しかし、これは成功します
var myAction = new Action<Animal>(a => Console.WriteLine("I'm an animal"));
PerformZebraAction(myAction); // I'm an animal
そしてこれも成功します:
var myAction = new Action<object>(a => Console.WriteLine("I'm an amoeba"));
PerformZebraAction(myAction); // I'm an amoeba
どうして?Action
はとして定義されているためAction<in T>
、つまりでありcontravariant
、の場合、これは「最大」aになるAction<Zebra> myAction
ことmyAction
ができますが、のAction<Zebra>
派生スーパークラスが少なくZebra
ても許容されます。
これは最初は直感的ではないかもしれませんが(たとえばAction<object>
、を必要なパラメータとして渡すにAction<Zebra>
はどうすればよいですか?)、ステップをアンパックすると、呼び出された関数(PerformZebraAction
)自体がデータ(この場合はZebra
インスタンス) )関数に-データは呼び出し元のコードからのものではありません。
このように高次関数を使用するという逆のアプローチのため、Action
が呼び出されるまでにZebra
、zebraAction
関数に対して呼び出されるのはより派生したインスタンスです(パラメーターとして渡されます)が、関数自体はあまり派生型を使用しません。
in
使用されるキーワードはどこにありますか?
Action<in T>
とFunc<in T, out TResult>
反変です。(私の例では、既存の不変(リスト)、共変(IEnumerable)、反変(アクション、
C#
わかりませんので、わかりません。
class A {}
class B : A {}
public void SomeFunction()
{
var someListOfB = new List<B>();
someListOfB.Add(new B());
someListOfB.Add(new B());
someListOfB.Add(new B());
SomeFunctionThatTakesA(someListOfB);
}
public void SomeFunctionThatTakesA(IEnumerable<A> input)
{
// Before C# 4, you couldn't pass in List<B>:
// cannot convert from
// 'System.Collections.Generic.List<ConsoleApplication1.B>' to
// 'System.Collections.Generic.IEnumerable<ConsoleApplication1.A>'
}
基本的に、1つの型のEnumerableを取る関数がある場合は、明示的にキャストしないと、派生型のEnumerableを渡すことができませんでした。
ただし、トラップについて警告するだけです。
var ListOfB = new List<B>();
if(ListOfB is IEnumerable<A>)
{
// In C# 4, this branch will
// execute...
Console.Write("It is A");
}
else if (ListOfB is IEnumerable<B>)
{
// ...but in C# 3 and earlier,
// this one will execute instead.
Console.Write("It is B");
}
とにかくそれは恐ろしいコードですが、それは存在し、C#4での動作の変化は、このような構成を使用する場合、微妙でバグを見つけるのが困難になる可能性があります。
MSDNから
次のコード例は、メソッドグループの共分散および反変のサポートを示しています。
static object GetObject() { return null; }
static void SetObject(object obj) { }
static string GetString() { return ""; }
static void SetString(string str) { }
static void Test()
{
// Covariance. A delegate specifies a return type as object,
// but you can assign a method that returns a string.
Func<object> del = GetString;
// Contravariance. A delegate specifies a parameter type as string,
// but you can assign a method that takes an object.
Action<string> del2 = SetObject;
}
現実の世界では、動物用シェルターがウサギをホストするたびに動物になるため、ウサギ用のシェルターの代わりに動物用のシェルターをいつでも使用できます。ただし、アニマルシェルターの代わりにウサギシェルターを使用すると、そのスタッフはトラに食べられる可能性があります。
あなたが持っている場合は、そのコードでは、これは手段IShelter<Animal> animals
あなたは、単に書くことができるIShelter<Rabbit> rabbits = animals
ならば、あなたが約束し、使用T
中IShelter<T>
だけなのでようなメソッドのパラメータとして:
public class Contravariance
{
public class Animal { }
public class Rabbit : Animal { }
public interface IShelter<in T>
{
void Host(T thing);
}
public void NoCompileErrors()
{
IShelter<Animal> animals = null;
IShelter<Rabbit> rabbits = null;
rabbits = animals;
}
}
アイテムをより一般的なものに置き換えます。つまり、分散を減らすか、逆分散を導入します。
現実の世界では、ウサギのサプライヤーがウサギを与えるたびにそれが動物であるため、動物のサプライヤーではなくウサギのサプライヤーをいつでも使用できます。ただし、ウサギの供給業者の代わりに動物の供給業者を使用する場合、トラに食べられる可能性があります。
あなたが持っている場合、コードでは、この手段があることISupply<Rabbit> rabbits
は、単に書くことができるISupply<Animal> animals = rabbits
ならば、あなたが約束し、使用T
中のISupply<T>
メソッドの戻り値はとても好きなだけのように:
public class Covariance
{
public class Animal { }
public class Rabbit : Animal { }
public interface ISupply<out T>
{
T Get();
}
public void NoCompileErrors()
{
ISupply<Animal> animals = null;
ISupply<Rabbit> rabbits = null;
animals = rabbits;
}
}
アイテムをより派生したものに置き換える、つまり、分散を大きくするか、coを導入する分散をます。
全体として、これはコンパイル時にチェックできるだけです、ジェネリック型を特定の方法で処理して型の安全性を維持し、誰も食べないように約束です。
contravariance
は興味深いです。私は運用要件を示すものとしてそれを読んでいます。より一般的なタイプは、それから派生したすべてのタイプのユースケースをサポートする必要があるということです。したがって、この場合、動物保護施設は、あらゆる種類の動物の保護をサポートできなければなりません。その場合、新しいサブクラスを追加すると、スーパークラスが壊れる可能性があります。つまり、サブタイプティラノサウルスレックスを追加すると、既存のアニマルシェルターが破壊される可能性があります。
コンバーターデリゲートは、両方の概念が一緒に機能することを視覚化するのに役立ちます。
delegate TOutput Converter<in TInput, out TOutput>(TInput input);
TOutput
メソッドがより具体的な型を返す共分散を表します。
TInput
メソッドに特定性の低い型が渡される場合の反変を表します。
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();