我在一本关于C#的教材中遇到了这些术语,但由于缺乏上下文,我很难理解它们。有没有一个简明扼要的解释说明它们是什么以及它们有什么作用?
澄清编辑:
协变接口:
澄清编辑:
协变接口:
interface IBibble<out T>
.
.
逆变接口:
interface IBibble<in T>
.
.
interface IBibble<out T>
.
.
逆变接口:
interface IBibble<in T>
.
.
<out T>
,您可以将接口引用视为层次结构中的上一级。<in T>
,您可以将接口引用视为层次结构中的下一级。IEnumerable<Animal> animals = GetFishes(); // returns IEnumerable<Fish>
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>
,只是因为所有的鱼都是动物,所以如果我们将类型更改为:
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中的in
和out
关键字专门将接口标记为一种或另一种。使用in
,您可以将通常为T的泛型类型放置在输入位置,这意味着方法参数和只写属性。
使用out
,您可以将泛型类型放置在输出位置,即方法返回值、只读属性和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
List fishes = GetAccessToFishes(); // 由于某种原因,返回了 List
Fish firstFist = fishes[0];
- DenninDalke这篇文章 是我读过的关于这个主题的最好文章。
简单来说,协变性 / 逆变性 / 不变性处理自动类型转换(从基类到派生类或者反之)。只有在对转换后的对象执行读取/写入操作时满足一些保证,才能进行这些类型转换。 阅读该文章以获取更多详细信息。