【问题标题】:Type Covariance runtime errors类型协方差运行时错误
【发布时间】:2016-07-25 13:33:42
【问题描述】:

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);

我相信上面的 sn-p 模仿了书中的例子 - 但我的 sn-p 没有运行时错误。

我错过了什么?正如书中所述,animals[0] = new Camel(); 应该如何引发运行时错误?

【问题讨论】:

  • animals 数组看起来像一组动物,但实际上是一组熊。您不能将骆驼添加到一系列熊中(显然熊会吃掉骆驼)。您的示例有动物 (X) 和熊 (Y),但缺少像骆驼这样的第二个子类。

标签: c# covariance


【解决方案1】:

出现这种错误的原因是什么?

因为它试图将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[]

【讨论】:

  • @Veverke:我不确定你的意思 - 但我现在正在查看你的更多相关示例。
  • 嗨乔恩,我的问题是关于本书的声明animals[0] = new Camel(); // Runtime error
  • @Veverke 这就是 Jon 的回答。
  • 没错,我记得这个例子只处理了一个派生类的动物。但它处理 2,并且在运行时数组类型将在 Bear 上关闭这一事实开始澄清事情。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    在下面一行:

    Animal[] animals = bears;
    

    你只是隐藏你的Bear[]Animal[]中。

    注意我说的是隐藏,因为你实际上并没有创建一个新数组。

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


    编译器不知道这一点。

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


    但是运行时会抱怨。

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

    虽然BearCamel都继承自Animal,但它们是两种不同的类型。

    【讨论】:

      【解决方案4】:

      对于此代码:

      Animal[] bears = new Bear[3];
      

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

      从 Bear[] 到 Animal[] 的协变数组转换可能会导致运行时 写操作异常。

      如果使用 IList 等 Generic:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多