了解C#中的协变和逆变接口

我在C#上阅读的教科书中遇到过这些问题,但是由于缺乏上下文,我很难理解它们。

有一个很好的简洁的解释,他们是什么,他们在那里有什么用?

编辑澄清:

协变界面:

interface IBibble<out T> . . 

反变化界面:

 interface IBibble<in T> . . 

使用<out T> ,您可以将接口引用视为一个向上的层次结构。

使用<in T> ,可以将界面引用作为一个向下在hiearchy中。

让我试着用更多的英文来解释它。

假设您正在从您的动物园中检索动物列表,并且您打算处理它们。 所有的动物(在你的动物园)都有一个名字和一个唯一的ID。 一些动物是哺乳动物,一些是爬行动物,一些是两栖动物,一些是鱼等,但都是动物。

所以,用你的动物列表(包含不同types的动物),你可以说所有的动物都有一个名字,所以显然所有动物的名字是安全的。

但是,如果你只有一个鸟类的名单,但需要像动物一样对待他们,这是否有效? 直观地说,它应该工作,但在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>只是因为所有的鱼都是动物,所以如果我们只是改变了types:

 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中的inout关键字专门将接口标记为一个或另一个。 在in ,允许将genericstypes(通常是T)放入input位置,这意味着方法参数和只写属性。

不用out ,你可以将通用types放在输出位置,这是方法返回值,只读属性和输出方法参数。

这将允许你做什么打算与代码:

 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>List<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 

以下是一些显示这些概念的video:

  • 协变和逆变 – VS2010 C#3的第1部分
  • 协变和逆变 – VS2010 C#中的第2部分
  • 协变和逆变 – VS2010 C#第3部分

这是一个例子:

 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 

这篇文章是我读过的最好的文章

简而言之,协变/反变换/不变性涉及自动types转换(从基础到派生,反之亦然)。 这些types转换只有在对铸造对象执行的读/写操作方面遵守一些保证时才是可能的。 阅读post了解更多详情。