我在C#上读的一本教科书中遇到过这些,但我很难理解它们,可能是因为缺乏上下文.
有没有一个很好的简洁的解释,说明它们是什么,它们有什么用处?
编辑以澄清:
协变接口:
interface IBibble<out T>
.
.
反变式界面:
interface IBibble<in T>
.
.
我在C#上读的一本教科书中遇到过这些,但我很难理解它们,可能是因为缺乏上下文.
有没有一个很好的简洁的解释,说明它们是什么,它们有什么用处?
编辑以澄清:
协变接口:
interface IBibble<out T>
.
.
反变式界面:
interface IBibble<in T>
.
.
使用<out T>
,可以将接口引用视为层次 struct 中向上的引用.
使用<in T>
,可以将接口引用视为层次 struct 中向下的引用.
让我试着用更多的英语来解释.
假设您正在从动物园检索动物列表,并打算对其进行处理.所有动物(在你的动物园里)都有一个名字和一个独特的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"));
在这里,编译器could允许这段代码,尽管该方法返回List<Animal>
只是因为所有鱼类都是动物,所以如果我们只是将类型更改为:
List<Animal> fishes = GetAccessToFishes();
fishes.Add(new Fish("Guppy"));
然后它就可以工作了,但是编译器不能确定您没有try 这样做:
List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>
Fish firstFist = fishes[0];
由于该列表实际上是一个动物列表,这是不允许的.
所以,逆协方差就是你如何处理对象引用,以及你可以对它们做什么.
C#4.0中的in
和out
个关键字专门将界面标记为一个或另一个.对于in
,您可以将泛型类型(通常是T)放在input个位置,这意味着方法参数,以及只写属性.
使用out
,可以将泛型类型放在output个位置,即方法返回值、只读属性和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