C#数组中的协变和逆变

13

在阅读维基百科关于协变和逆变的文章section时,我遇到了以下加粗的句子:

首先考虑数组类型构造函数:从类型Animal我们可以得到类型Animal[](“动物数组”)。我们应该将其视为
- 协变:一个Cat[]是一个Animal[] - 逆变:一个Animal[]是一个Cat[] - 还是不变?
如果我们希望避免类型错误,并且该数组支持读写元素,则只有第三个选择是安全的。显然,并非每个Animal[]都可以被视为Cat[],因为客户端从数组中读取时会期望得到猫,但是Animal[]可能包含例如Dog。因此,逆变规则不安全。
相反,Cat[]不能被视为Animal[]。应该总是能够将Dog放入Animal[]中。对于协变数组,无法保证这是安全的,因为后备存储可能实际上是猫的数组。因此,协变规则也不安全 - 数组构造函数应该是不变的。请注意,这仅适用于可变数组;对于不可变(只读)数组,协变规则是安全的。

我理解这个概念,只是想要一个C#中的“无法保证安全”的例子


http://blogs.msdn.com/b/ericlippert/archive/2007/10/17/covariance-and-contravariance-in-c-part-two-array-covariance.aspx - SLaks
1
我不知道为什么这个问题被关闭为链接问题的重复。更相关的问题是:为什么数组协变性被认为是如此可怕 - nawfal
2个回答

23

在编译时不安全。换句话说,有些代码按照语言规则是合法的,但在执行时会失败,而没有任何显式的类型转换来警告“这可能会失败”。CLR确保只有在执行时才能成功写入有效数据。例如:

string[] strings = new string[1];
object[] objects = strings;
objects[0] = new object();

这将在执行时引发异常 (ArrayTypeMismatchException)。另一种选择是允许在执行时进行,此时 strings[0] 将成为一个非字符串对象的引用,这显然是不好的。

还请查看最近的博客文章:


当你说“CLR 在执行时确保它的安全性”,你是指它会抛出一个异常吗?这就是它确保在执行时安全的方式吗? - Mike Perrenoud
@MichaelPerrenoud: 是的,没错。我会编辑以澄清这一点。 - Jon Skeet
太棒了,谢谢!我只是想确认我理解得没错。 - Mike Perrenoud
1
@JonSkeet 我测试了你的代码,它抛出了ArrayTypeMismatchException异常。但是我设置了一个断点,并通过DebuggerVisualiser手动将objects [0] = new object(),它没有抛出任何错误并且可以正常工作。稍后检查strings [0] .GetType() 返回System.Object。 - Sriram Sakthivel
@SriramSakthivel 您是正确的。在发表评论之前,我应该先进行测试。 - Johnbot
显示剩余2条评论

1

我认为他们想要表达的是:

Dog dog = new Dog();
Cat[] cats = new Cat[] { catOne, catTwo, catThree };
Animal[] animals = cats;
animals.Add(dog);

这段代码的第三行是不合法的,因为你应该总是能够执行第四行(将一个 Dog 添加到一个 Animal 数组中)。但如果第三行是合法的,那么第四行就是不合法的(因为你不能将一个 Dog 添加到一个 Cat 数组中)。

2
你描述了 C# 团队本可以实现这个的方式,但事实上,第三行确实编译通过了,而第四行在运行时被捕获了(技术上讲,第四行并没有编译通过,因为数组没有 Add 方法,但是 animals[0] = dog 会在运行时被捕获)。 - Hutch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接