了解 C# 中的协变和逆变接口 [英] Understanding Covariant and Contravariant interfaces in C#

查看:32
本文介绍了了解 C# 中的协变和逆变接口的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在一本关于 C# 的教科书中遇到过这些,但我很难理解它们,可能是由于缺乏上下文.

I've come across these in a textbook I am reading on C#, but I am having difficulty understanding them, probably due to lack of context.

是否对它们是什么以及它们有什么用处有一个很好的简明解释?

Is there a good concise explanation of what they are and what they are useful for out there?

编辑以澄清:

协变接口:

interface IBibble<out T>
.
.

逆变接口:

interface IBibble<in T>
.
.

推荐答案

使用,您可以将接口引用视为层次结构中向上的引用.

With <out T>, you can treat the interface reference as one upwards in the hierarchy.

使用,您可以将接口引用视为层次结构中向下的一个.

With <in T>, you can treat the interface reference as one downwards in the hiearchy.

让我试着用更多的英语来解释它.

Let me try to explain it in more english terms.

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

Let's say you are retrieving a list of animals from your zoo, and you intend to process them. All animals (in your zoo) have a name, and a unique ID. Some animals are mammals, some are reptiles, some are amphibians, some are fish, etc. but they're all animals.

因此,通过您的动物列表(其中包含不同类型的动物),您可以说所有动物都有一个名称,因此显然获得所有动物的名称是安全的.

So, with your list of animals (which contains animals of different types), you can say that all the animals have a name, so obviously it would be safe to get the name of all the animals.

但是,如果您只有一个鱼类列表,但需要像对待动物一样对待它们,这行得通吗?直觉上,它应该可以工作,但在 C# 3.0 及之前,这段代码将无法编译:

However, what if you have a list of fishes only, but need to treat them like animals, does that work? Intuitively, it should work, but in C# 3.0 and before, this piece of code will not compile:

IEnumerable<Animal> animals = GetFishes(); // returns IEnumerable<Fish>

这样做的原因是编译器不知道"您的意图,或者可以在您检索到动物集合后对其进行处理.据它所知,可能有一种方法通过 IEnumerable 将对象放回列表中,这可能允许您将不是鱼的动物放入应该只包含鱼的集合.

The reason for this is that the compiler doesn't "know" what you intend, or can, do with the animals collection after you've retrieved it. For all it knows, there could be a way through IEnumerable<T> to put an object back into the list, and that would potentially allow you to put an animal that isn't a fish, into a collection that is supposed to contain only fish.

换句话说,编译器不能保证这是不允许的:

In other words, the compiler cannot guarantee that this is not allowed:

animals.Add(new Mammal("Zebra"));

所以编译器直接拒绝编译你的代码.这是协方差.

So the compiler just outright refuses to compile your code. This is covariance.

让我们看看逆变.

既然我们的动物园可以处理所有的动物,它当然可以处理鱼,所以让我们尝试在我们的动物园中添加一些鱼.

Since our zoo can handle all animals, it can certainly handle fish, so let's try to add some fish to our zoo.

在 C# 3.0 及之前,这不会编译:

In C# 3.0 and before, this does not compile:

List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>
fishes.Add(new Fish("Guppy"));

在这里,编译器可以允许这段代码,即使该方法返回List仅仅是因为所有的鱼都是动物,所以如果我们只是改变了键入此:

Here, the compiler could allow this piece of code, even though the method returns List<Animal> simply because all fishes are animals, so if we just changed the types to this:

List<Animal> fishes = GetAccessToFishes();
fishes.Add(new Fish("Guppy"));

然后它会起作用,但编译器无法确定您不是在尝试这样做:

Then it would work, but the compiler cannot determine that you're not trying to do this:

List<Fish> fishes = GetAccessToFishes(); // for some reason, returns List<Animal>
Fish firstFist = fishes[0];

由于列表实际上是动物列表,所以这是不允许的.

Since the list is actually a list of animals, this is not allowed.

因此,逆变和协变是您处理对象引用的方式以及您可以对它们做什么.

So contra- and co-variance is how you treat object references and what you're allowed to do with them.

C# 4.0 中的inout 关键字专门将接口标记为一个或另一个.使用 in,您可以将泛型类型(通常是 T)放在 input 位置,这意味着方法参数和只写属性.

The in and out keywords in C# 4.0 specifically marks the interface as one or the other. With in, you're allowed to place the generic type (usually T) in input-positions, which means method arguments, and write-only properties.

使用out,您可以将泛型类型放在输出-位置,即方法返回值、只读属性和输出方法参数.

With out, you're allowed to place the generic type in output-positions, which is method return values, read-only properties, and out method parameters.

这将允许您对代码执行要执行的操作:

This will allow you to do what intended to do with the code:

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> has both in- and out-directions on T, so it is neither co-variant nor contra-variant, but an interface that allowed you to add objects, like this:

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

<小时>

以下是一些展示概念的视频:


Here's a few videos that shows the concepts:

这是一个例子:

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;
        }
    }
}

没有这些标记,以下内容可以编译:

Without these marks, the following could compile:

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

这篇关于了解 C# 中的协变和逆变接口的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆