我在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中的inout个关键字专门将界面标记为一个或另一个.对于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

.net相关问答推荐

在 Rx 中,处理线程安全是否是消费者(IObserver)的责任?

如何知道变量是否只是指向另一个对象的pointer或者它是否可以独立存在

process.WaitForExit() 异步

在 C# 中生成随机小数

在 .NET 中获取执行 exe 路径的最佳方法是什么?

maxRequestLength 的最大值?

.NET 中是否有一种简单的方法来获得数字的st、nd、rd和th结尾?

如何获取 Sql Server 数据库中所有模式的列表

为什么循环引用被认为是有害的?

每第 N 个字符/数字拆分一个字符串/数字?

ILMerge 最佳实践

运算符重载 ==, !=, Equals

双倍的? = 双倍? + 双倍?

如何将 System.Type 转换为其可为空的版本?

ReaderWriterLockSlim 什么时候比简单的锁更好?

将 SignalR 2.0 .NET 客户端重新连接到服务器集线器的最佳实践

ADO.NET Entity Framework:更新向导不会添加表

如何修改 KeyValuePair 值?

如何为我的 C# 应用程序创建产品密钥?

序列化和反序列化 .NET 对象的最快方法