键入协方差运行时错误

本文关键字:运行时错误 方差 | 更新日期: 2023-09-27 17:58:29

Joseph Albahari和Ben Albahari(O’Reilly)的《坚果壳中的C#6.0》。

版权所有2016 Joseph Albahari和Ben Albahari,978-1-491-92706-9。

在第123-124页,关于类型协方差的陈述:

由于历史原因,数组类型支持协方差。这意味着如果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();应该如何抛出运行时错误?

键入协方差运行时错误

这种错误背后的原因是什么?

因为它试图将Camel存储到运行时类型为Bear[]的数组中。类型为Bear[]的数组只能存储对Bear或子类实例的引用。Animal[]的编译时类型只表示它可能能够存储Camel引用,并且从数组的中获得的任何引用都肯定是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[]

您的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();

在以下行中:

Animal[] animals = bears;

您只是将您的Bear[]隐藏到Animal[]中。

请注意,我说隐藏,因为您实际上并不是在创建一个新数组。

bearsanimals都指向同一个Bear[],唯一的区别是animals将该引用隐藏为Animal[]


编译器不知道这一点。

对于编译器,如果您想在animals上存储DolphinLion,它会允许您这样做,因为所有这些元素的类型都是Animal


然而,运行时会抱怨。

由于animals隐藏了Bear[],因此不允许向其添加Camel

尽管BearCamel都继承了Animal,但它们是两种不同的类型。

对于此代码:

Animal[] bears = new Bear[3];

如果你在VS中安装了Resharper,它会发出相同的警告:

从Bear[]到Animal[]的共变体数组转换可能导致运行时写入操作出现异常。

如果您使用诸如IList:之类的Generic

IList<Animal> bears = new List<Bear>();
bears.Add(new Camel());

第一行不允许您进行编译,这更安全,可以防止可能的运行时异常。