约瑟夫·阿尔巴哈里和本·阿尔巴哈里(奥莱利)在果壳中的C#6.0。版权所有2016 Joseph Albahari和Ben Albahari,978-1-491-92706-9。
陈述,在第123-124页,关于类型协方差:
数组由于历史原因,数组类型支持协方差。这个 意味着如果B子类A(并且两者都是),则B []可以转换为A [] 参考类型)。
例如:
Bear[] bears = new Bear[3];
Animal[] animals = bears; // OK
这种可重用性的缺点是元素分配可能会失败 在运行时:
animals[0] = new Camel(); // Runtime error
这种错误背后的原因是什么? 如果将Bear实例分配给Animal实例,则会抛出运行时错误?我不明白为什么它应该(通过允许这样的任务,编译器需要承担责任,说明"好吧,我会让你用这个对象做动物可以做的一切。"自熊是一种动物,这不会引起任何问题。
我创建了自己的场景来测试上面的内容:
public class X
{
public int Num { get; set; }
public void Method_1()
{
Console.WriteLine("X");
}
public virtual void Method_2()
{
Console.WriteLine(Num);
}
}
public class Y : X
{
public Y()
{
Num = 1000;
}
}
X[] arrayX = new X[] { new X { Num = 1000 }, new X { Num = 999 }, new X { Num = 51762 } };
Y[] arrayY = new Y[] { new Y { Num = 5 }, new Y { Num = 6 }, new Y { Num = 7 } };
X x = new X { Num = 1000 };
Y y = new Y { Num = 50 };
x = y;
arrayX = arrayY;
arrayX[2] = new Y { Num = 1 };
// will print 5,6,1 - no runtime errors faced
foreach (var e in arrayX)
Console.WriteLine(e.Num);
我相信上面的代码段模仿了这本书的例子 - 但是我的代码片段没有运行时错误。
我错过了什么?正如本书所述,animals[0] = new Camel();
如何抛出运行时错误?
答案 0 :(得分:4)
这种错误背后的原因是什么?
因为它试图将Camel
存储到运行时类型为Bear[]
的数组中。类型为Bear[]
的数组只能 存储对Bear
或子类实例的引用。编译时类型Animal[]
仅表示可能能够存储Camel
引用,并且您获得的任何引用 out 数组肯定是Animal
实例或子类。
你的例子不同。当我们删除所有属性等(这些都无关紧要)时,你已经得到了:
X[] arrayX = new Y[3];
arrayX[2] = new Y();
没关系 - 这是在一个执行时类型为Y
的数组中存储对Y[]
对象的引用。没问题。
为了证明与本书相同的问题,你需要第三节课:
class Z : X {}
X[] arrayX = new Z[3];
arrayX[2] = new Y(); // Bang - can't store a Y reference in a Z[]
答案 1 :(得分:1)
X和Y的示例与书中的示例完全不同。 如果你想模仿书中的那个,创建抽象基类X,然后使Y和Z从它派生。然后,玩弄它。 书中的那个意味着:
class Program
{
static void Main(string[] args)
{
Bear[] bears = new Bear[3];
Animal[] animals = bears;
animals[0] = new Camel(); //will throw on runtime
}
}
public abstract class Animal { }
public class Camel : Animal { }
public class Bear : Animal { }
正如Jon已经说过的,animals
的运行时类型将是Bear[]
,它无法存储Camel
的实例。
另请注意,这是协变阵列转换的可能错误,但对List
等其他集合不会发生:
List<Bear> bearsList = new List<Bear>();
List<Animal> animalsList = bearsList; //won't compile because of this error
animalsList[0] = new Camel();
答案 2 :(得分:1)
在以下行中:
Animal[] animals = bears;
您只是将<{1}}隐藏<{1}}到Bear[]
。
请注意,我说隐藏,因为您实际上并没有创建新数组。
Animal[]
和bears
都指向相同的animals
,唯一的区别是Bear[]
将该引用隐藏为animals
。
编译器不知道这一点。
对于编译器,如果要在Animal[]
上存储Dolphin
或Lion
,它将允许您这样做,因为所有这些元素都是{{1}类型}}
然而,运行时会抱怨。
由于animals
隐藏Animal
,因此不允许向其添加animals
。
虽然Bear[]
和Camel
都来自Bear
,但它们是两种不同的类型。
答案 3 :(得分:0)
对于此代码:
Animal[] bears = new Bear[3];
如果您在VS中安装了Resharper,它会发出相同的警告:
从Bear []到Animal []的共变阵列转换可能会导致运行时 写操作异常。
如果您使用Generic,例如IList:
IList<Animal> bears = new List<Bear>();
bears.Add(new Camel());
第一行不会让你编译,这更安全,防止它可能的运行时异常。