C#用两种方式实现了可变性。今天,我们讲讲有缺陷的那种方法。
自从C# 1.0,存储引用类型元素的Array是可变的。所以下面的代码是合法的:
Animal[] animals = new Griaffe[10];
因为Giraffe要比Animal小,并且“生成某种类型的Array”是协变操作,Giraffe[]比Aniaml[]小,所以实例可以被赋予那个变量。
很不幸,在这种类型的可变性是有缺陷的。因为Java可以这么做,CLR的设计者想要支持像Java一样的语言,所以它被添加到CLR中。又因为它存在于CLR中,所以我们紧接着把它加到C#里面。这个决定在当时很有争议,我对此不满,但是我们也阻止不了。
为什么这是有缺陷的?因为把Turtle添加到Animals Arrarys应该总是合法的。但是因为数组的协变性和运行时的协变性,你不能保证一个Turtle可以被添加到Animals数据中,因为这个Animals数组的实例可能是Giraffes。(就像上面说的)
这意味着我们把一个本可以被编译器捕获的Bug变成了只能在运行时被捕获的Bug。这也意味着每次你向数组添加一个对象,我们必须执行一次运行时检查来保证类型匹配,或者在类型不匹配的时候抛出异常。如过你想数组中添加了很多这样的东西,那么代价是非常昂贵的。
不幸的是。。我们还要忍受这个。。(是的,我试了一下。。)
我想要借着这个机会阐述Part One中出现的一些观点。
首先,通过“子类”和“父类”,我想表达的意思是,“它们位于基类的继承链上”。我没有表达“可以替换的”这个概念。通过“大于”和“小于”,我特意不想表达“子类”和“基类”的意思。的确,子类是比父类都小,但小的类不一定是大的类的子类。Giraffe[]比Animal[]和System.Array都小,很明显Giraffe[]是System.Array的子类,但**不是**Animal[]的子类。因此,“更小”这种关系比“是一个”这种关系更宽泛。我想要区分一下赋值兼容性和继承。
下次我们将讨论C#2.0中可靠的可变性。