我很难理解如何在现实世界中使用协变和逆变。
到目前为止,我看到的唯一示例是相同的旧数组示例。
object[] objectArray = new string[] { "string 1", "string 2" };
如果我能看到它在其他地方使用,那么很高兴看到一个允许我在开发过程中使用它的示例。
我很难理解如何在现实世界中使用协变和逆变。
到目前为止,我看到的唯一示例是相同的旧数组示例。
object[] objectArray = new string[] { "string 1", "string 2" };
如果我能看到它在其他地方使用,那么很高兴看到一个允许我在开发过程中使用它的示例。
// 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();
为了完整性……</p>
// 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??
这是我汇总的内容以帮助我了解差异
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
假设您有一个 Person 类和一个派生自它的类 Teacher。您有一些以 anIEnumerable<Person>
作为参数的操作。在您的 School 课程中,您有一个返回IEnumerable<Teacher>
. 协方差允许您直接将该结果用于采用 的方法,将IEnumerable<Person>
派生程度更高的类型替换为派生程度较低(更通用)的类型。与直觉相反,逆变允许您使用更通用的类型,其中指定了更派生的类型。
另请参阅MSDN 上泛型中的协变和逆变。
课程:
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());
这是一个使用继承层次结构的简单示例。
给定简单的类层次结构:
在代码中:
public abstract class LifeForm { }
public abstract class Animal : LifeForm { }
public class Giraffe : Animal { }
public class Zebra : Animal { }
不变性(即没有用in
orout
关键字修饰的泛型类型参数)
貌似,这样的方法
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
任何子类添加到参数中,并且会违反传递给方法的任何派生类型集合的类型。(在这里,恶意方法会尝试添加一个to )。幸运的是,编译器保护我们免受这种危险。LifeForm
lifeForms
Zebra
var myGiraffes
协方差(带有修饰的参数化类型的泛型out
)
协方差广泛用于不可变集合(即不能从集合中添加或删除新元素的情况)
上述问题的解决方案——即将派生程度更高的类型的集合传递给接受派生程度较低的超类collection<Giraffe>
的集合的函数——是确保使用协变的泛型集合类型,例如(定义为)。没有更改集合的方法,并且由于协变,任何具有子类型的集合现在都可以传递给该方法:collection<LifeForm>
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
.
var myGiraffes = new List<Giraffe>
{
new Giraffe(), // "Jerry"
new Giraffe() // "Melman"
};
PrintLifeForms(myGiraffes); // All good!
逆变(带有修饰的参数化类型的泛型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
,这意味着对于Action<Zebra> myAction
,那myAction
最多可以是 a Action<Zebra>
,但是具有较少派生超类的参数的动作Zebra
也是可以接受的。
虽然一开始这可能是不直观的(例如,如何将 anAction<object>
作为参数传递Action<Zebra>
?),如果你解包这些步骤,你会注意到被调用的函数 ( PerformZebraAction
) 本身负责传递数据(在这种情况下是一个Zebra
实例) 到函数 - 数据不是来自调用代码。
由于以这种方式使用高阶函数的反向方法,在Action
调用时,它是针对函数调用的派生程度更高的Zebra
实例zebraAction
(作为参数传递),尽管函数本身使用派生程度较低的类型。
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
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>'
}
基本上,只要您有一个采用一种类型的 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 中不断变化的行为可能会引入微妙且难以发现的错误。
在现实世界中,您始终可以使用动物收容所而不是兔子收容所,因为每次动物收容所收容兔子时,它都是动物。然而,如果你使用兔子收容所而不是动物收容所,它的员工可能会被老虎吃掉。
在代码中,这意味着如果您有一个,IShelter<Animal> animals
您可以简单地编写IShelter<Rabbit> rabbits = animals
if您承诺并使用T
only IShelter<T>
as 方法参数,如下所示:
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
if您承诺并T
在ISupply<T>
only as 方法中使用返回值,如下所示:
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;
}
}
并用更衍生的项目替换项目,即增加方差或引入协方差。
总而言之,这只是您的一个编译时可检查的承诺,即您将以某种方式处理泛型类型以保持类型安全并且不让任何人吃掉。
你可能想读一读,以使你的头脑更加清楚。
来自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;
}
转换器委托帮助我可视化这两个概念一起工作:
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();