键入协方差运行时错误

时间:2016-07-25 13:33:43

标签: c# covariance

  约瑟夫·阿尔巴哈里和本·阿尔巴哈里(奥莱利)在果壳中的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();如何抛出运行时错误?

4 个答案:

答案 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[]上存储DolphinLion,它将允许您这样做,因为所有这些元素都是{{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());

第一行不会让你编译,这更安全,防止它可能的运行时异常。