这个协方差相关代码背后的意义是什么?

What is the point behind this covariance related code?

我一直在读一本名为 c# 7.0 in a Nutshell by O'REILLY 的书,主题:方差不是自动的。有一个例子有 2 classes,Animal 和 Bear,其中 Animal>Bear:

public class Animal { }
public class Bear: Animal { }

还有一个像这样的class:

public class Stack<T>
{
    private int position;
    T[] data = new T[100];
    public void Push(T obj) => data[position++] = obj;
    public T Pop() => data[--position];
}

继续有两个相同的版本class:

public class ZooCleaner1
{
    public static void Wash(Stack<Animal> animals) { }
}

和:

public class ZooCleaner2
{
    public static void Wash<T>(Stack<T> animals) where T: Animal { }
}

它解释说如果我尝试写:

ZooCleaner1.Wash(bears);
ZooCleaner2.Wash(bears);

第一行出现编译时错误,表明无法将 Bear 转换为 Animal。但是第二行是正确的并且工作正常。由于我是这个 topc 的新手,我无法理解这两行之间的差异,我认为它们都接受 Stack<Animal> 为什么我们需要使用条件泛型?

Stack<Animal> 表示任何 Animal 类型的对象堆栈。 Stack<T> where T: Animal 表示 单一 类型的堆栈,只要该类型继承自 Animal.

您不能使用 Stack<Bear> 代替声明为 Stack<Animal> 的参数,因为如果您 可以 ,则该方法可以推送 Fish 放到熊堆上。当使用 Bears 堆栈的方法将它从堆栈中弹出时,想象一下它弹出一条鱼时的惊喜!

另一方面,第二种方法是 generic,这意味着它可以接受任何类型的堆栈,只要该类型继承自 Animal因此,如果该方法获得 Stack<Bear>,它可以 将另一个 Bear 压入堆栈。尝试推送 Fish 会出现运行时错误。

我不会称之为 "covariance"。 This 是通用方差。您的代码仅演示通用约束。

让我们看看我们可以在每个 Wash 方法中做什么。在第一个Wash方法中,我们可以:

public static void Wash(Stack<Animal> animals) { 
    animals.Push(new Animal());
    Animal a = animals.Pop();
}

现在假设你有一个 Stack<Bear> bears;,你想把它传递给第一个 Wash。如果编译器允许您这样做,您是否看到这将如何产生矛盾?您实际上不能将 Animal 添加到 Stack<Bear>!但就Wash而言,加一个Animal完全没问题,因为它只知道可以接受一个Stack<Animal>!

因此,Stack<Bear>不是Stack<Animal>的子类型,因为前者不能加Animal,后者可以。

在第二个Wash方法中,虽然可以传bears给它,但是不能再给ti加上Animals:

public static void Wash<T>(Stack<T> animals) where T: Animal { 
    animals.Push(new Animal()); // error
    Animal a = animals.Pop();
}

因为编译器不确定 Stack<T>Stack<Animal>。它 可能是 ,但它也可能是 Stack<Bear>,或 Stack<Unicorn>Stack<SomeOtherSubclassOfAnimal>,对吗?