简体   繁体   English

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

[英]Understanding Covariant and Contravariant interfaces in 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.我在一本关于 C# 的教科书中遇到过这些,但我很难理解它们,可能是由于缺乏上下文。

Is there a good concise explanation of what they are and what they are useful for out there?是否有一个关于它们是什么以及它们在那里有用的简洁解释?

Edit for clarification:编辑以澄清:

Covariant interface:协变接口:

interface IBibble<out T>
.
.

Contravariant interface:逆变接口:

interface IBibble<in T>
.
.

With <out T> , you can treat the interface reference as one upwards in the hierarchy.使用<out T> ,您可以将接口引用视为层次结构中的向上引用。

With <in T> , you can treat the interface reference as one downwards in the hiearchy.使用<in T> ,您可以将接口引用视为层次结构中的一个向下引用。

Let me try to explain it in more english terms.让我试着用更多的英语来解释它。

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.所有动物(在你的动物园里)都有一个名字和一个唯一的 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.因此,通过您的动物列表(其中包含不同类型的动物),您可以说所有动物都有一个名称,因此显然获得所有动物的名称是安全的。

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:直觉上,它应该可以工作,但在 C# 3.0 及之前,这段代码将无法编译:

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

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.就它所知,可能有一种方法通过IEnumerable<T>将对象放回列表中,这可能允许您将不是鱼的动物放入应该只包含的集合中鱼。

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.这就是协方差。

Let's look at contravariance.让我们看看逆变。

Since our zoo can handle all animals, it can certainly handle fish, so let's try to add some fish to our zoo.既然我们的动物园可以处理所有的动物,它当然可以处理鱼,所以让我们尝试在我们的动物园中添加一些鱼。

In C# 3.0 and before, this does not compile:在 C# 3.0 及之前,这不会编译:

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

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>只是因为所有的鱼都是动物,所以如果我们只是将类型更改为:

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.因此,逆变和协变是您处理对象引用的方式以及您可以用它们做什么。

The in and out keywords in C# 4.0 specifically marks the interface as one or the other. C# 4.0 中的inout关键字专门将接口标记为一个或另一个。 With in , you're allowed to place the generic type (usually T) in input -positions, which means method arguments, and write-only properties.使用in ,您可以将泛型类型(通常是 T)放在输入位置,这意味着方法参数和只写属性。

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.使用out ,您可以将泛型类型放在output -positions 中,即方法返回值、只读属性和 out 方法参数。

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> 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: List<T>List<T>有入方向和出方向,因此它既不是协变也不是逆变,而是一个允许您添加对象的接口,如下所示:

interface IWriteOnlyList<in T>
{
    void Add(T value);
}

would allow you to do this:将允许你这样做:

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:以下是一些展示这些概念的视频:

Here's an example:下面是一个例子:

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

or this:或这个:

public List<Base> GetBases() ...
List<Descendant> descendants = GetBases(); <-- uh-oh, we try to treat all Bases
                                               as Descendants

This post is the best I've read on the subject 这篇文章是我读过的关于这个主题的最好的文章

In short, covariance / contravariance /invariance deals with automatic type casting (from base to derived and vice-versa).简而言之,协方差/逆变/不变性处理自动类型转换(从基础到派生,反之亦然)。 Those type casts are possible only if some guarantees are respected in terms of read / write actions performed on the casted objects.只有在对转换对象执行的读/写操作方面遵守某些保证时,才能进行这些类型转换。 Read the post for more details.阅读帖子了解更多详情。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM