我一直在读一本名为c# 7.0 in a Nutshell by O'REILLY的书,主题:方差不是自动的。有一个示例有2个类,动物和熊,其中Animal> Bear:
public class Animal { }
public class Bear: Animal { }
还有一个这样的类:
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];
}
继续,有2个相同类别的版本:
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
。但是第二行是正确的并且可以正常工作。由于我是本教程的新手,所以我无法理解这两行之间的区别,我认为它们都接受Stack<Animal>
,为什么我们需要使用条件泛型?
Stack<Animal>
表示任何Animal
类型的对象的堆栈。Stack<T> where T: Animal
表示single类型的堆栈,只要该类型继承自Animal
。
您不能使用Stack<Bear>
代替声明为Stack<Animal>
的参数,因为如果您[[could,则该方法可能会将Fish
推入熊的堆栈。当使用Bear
堆栈的方法将其从堆栈中弹出时,请想象一下当它从一条鱼中弹出时会感到惊讶!
generic
,这意味着它可以接受任何类型的堆栈,只要该类型是从Animal
继承的,那么如果该方法获得Stack<Bear>
,则它可以only将另一个Bear
推入堆栈。尝试推送Fish
将是运行时错误。让我们看看在每种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
传递给它,但是您不能再将Animal
添加到ti:
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>
,对吧?