c# - 了解 C# 中的协变和逆变接口(interface)

标签 c# .net interface covariance contravariance

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

是否有关于它们是什么以及它们在那里有什么用处的简洁明了的解释?

编辑澄清:

协变接口(interface):

interface IBibble<out T>
.
.

逆变接口(interface):

interface IBibble<in T>
.
.

最佳答案

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

<in T> ,您可以将接口(interface)引用视为层次结构中的一个向下。

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

假设您要从动物园中检索动物列表,并且您打算处理它们。所有动物(在你的动物园里)都有一个名字和一个唯一的 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"));

在这里,编译器可以允许这段代码,即使该方法返回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];

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

因此,反方差和协方差是您如何处理对象引用以及您可以对它们执行的操作。

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

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),如下所示:

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

关于c# - 了解 C# 中的协变和逆变接口(interface),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2719954/

相关文章:

c# - protobuf-net List<>继承反序列化

c# - LINQ to Entities - 解析/过滤对象集合

c# - 网络服务器 : reading http request from stream

java - spring 框架中 Service 和 DAO 接口(interface)的主要目的是什么?

java - 如何将对象列表转换为接口(interface)列表?

java - 为什么父类和子类都实现相同的接口(interface)?

c# - 如何防止 DataGridTemplateColumn 根据某些条件进入编辑模式?

c# - Gtk.NodeView.AddNode 中的异常

c# - DirectoryServices.Protocols.SearchRequest 返回 GUID 的多个结果

c# - 应该在引发事件的类内部还是外部声明委托(delegate)?