【问题标题】:Polymorphism and casting多态性和铸造
【发布时间】:2014-05-29 03:03:38
【问题描述】:

我想了解 c# 中的多态性,所以通过尝试几种构造,我想出了以下案例:

class Shape
{
    public virtual void Draw()
    {
        Console.WriteLine("Shape.Draw()");
    }
}

class Circle : Shape
{
    public override void Draw()
    {
        Console.WriteLine("Circle.Draw()");
    }
}

我知道,为了将 Draw() 消息发送到几个相关对象,以便它们可以根据自己的实现采取行动,我必须更改(在这种情况下)形状“指向”的实例:

Shape shape = new Circle();
shape.Draw(); //OK; This prints: Circle.Draw()

但是为什么,当我这样做时:

Circle circle = new Circle();
circle.Draw(); //OK; This prints: Circle.Draw()

Shape shape = circle as Shape; // or Shape shape = (Shape)circle;
shape.Draw();

它打印:“Circle.Draw()”

为什么它在演员阵容后调用 Circle.Draw() 而不是 Shape.Draw()?这是什么原因?

【问题讨论】:

  • 强制转换不会改变实际实例 - 只是指向它的变量。
  • +1 好问题,感谢您真正了解 OOP 中发生的事情。

标签: c# polymorphism overriding virtual


【解决方案1】:

您将覆盖继承类中的方法,因此无论您的引用是否指向不太具体的基类,它始终是被调用的版本。

如果您想调用Shape 中的版本,您需要Shape 类型的实例,而不仅仅是该类型的引用。

【讨论】:

  • 我同意你的第一句话,但我更感兴趣的是为什么。发生这种情况的内部机制是什么。
  • @user3105717 好吧,我不想对此发表评论太随意,但我也许可以在查看我的想法后进行编辑。需要注意的一件事是,您使用了一些 Objective-C 风格的措辞,这可能是您困惑的一部分。 C# 的行为不是这样,我不会向“类”或其他任何东西发送“消息”。它比那更静态。它更像是在编译时,编译器确定底层类型,找到该方法的定义并内联或引用它。不要把这个解释当成事实,因为我需要做一些审查才能给出一个完全准确的答案。
【解决方案2】:

强制转换不会更改对象的运行时类型以及每个实例具有的特定虚拟方法的实现。

请注意,您作为样本的以下 2 个案例是相同的:

Shape shape = new Circle();
shape.Draw(); //OK; This prints: Circle.Draw()

和:

Circle circle = new Circle();
Shape shape = circle as Shape;
shape.Draw();

第一个基本上是第二个的较短版本。

【讨论】:

  • 所以如果我理解正确的话,从一般的角度来看: 1.运行时检查变量形状的运行时类型。 2.确定是Circle类型。第 3、4、n 步是什么,所以最后调用 Circle.Draw()?
  • @user3105717 3:检查 Circle 是否覆盖 Draw -> 是调用它,否 -> 转到父级并检查...(实际上它很简单,因为编译器/JIT 能够计算提前取出大部分内容 - “调用在对象的(运行类型)类型中设置的 Draw 的任何实现”)。有关内部工作的更多详细信息,请参阅 LordTakkera 的回答 - 或查看维基百科上的 VMT
【解决方案3】:

其他答案是绝对正确的,但要尝试更深一层:

多态性是通过使用称为虚函数指针表(vTable)的东西来实现的。本质上,你会得到类似的东西:

形状 -> Shape.Draw()

圆 -> Circle.Draw()

当您调用标记为“虚拟”的函数时,编译器会执行 typeof,并调用该函数的派生度最高的实现,该实现是类型继承树的一部分。由于 Circle 继承自 Shape,并且您有一个 Circle 对象(如前所述,转换不会影响基础类型),因此调用了 Circle.Draw。

显然这是对实际发生的事情的过度简化,但希望它有助于解释为什么多态行为会以它的方式行事。

【讨论】:

    【解决方案4】:

    正如其他人所提到的,强制转换对象不会改变实际实例;相反,强制转换允许变量从对象层次结构的更高层假定实例的特征子集。

    为了说明为什么它需要以这种方式工作,请考虑以下示例:

    //Some buffer that holds all the shapes that we will draw onscreen
    List<Shape> shapesOnScreen = new List<Shape>();
    
    shapesOnScreen.Add(new Square());
    shapesOnScreen.Add(new Circle());
    
    //Draw all shapes
    foreach(Shape shape in shapesOnScreen)
    {
        shape.Draw();
    }
    

    在 foreach 循环中调用 Draw() 将调用派生实例的 Draw() 方法,即 Square.Draw() 和 Circle.Draw()。在此示例中,这允许您绘制每个单独的形状,而无需确切知道您在运行时绘制的形状。你只知道你需要一个形状,让形状处理它的绘制方式。

    如果不是这种情况(这适用于其他语言的继承,而不仅仅是 C#),您将无法使用除 Shape.Draw() 之外的任何东西。

    【讨论】:

      【解决方案5】:

      我用is-a来解释一下,因为形状is是一个圆:

      Shape shape = circle as Shape;
      

      代码完美地解释了自己,你指的是圆as一个形状,形状根本没有改变,它is仍然是一个圆,虽然它is也是一个形状。

      你甚至可以检查is是不是一个圈子:

      if (shape is Circle)
          Console.WriteLine("The shape is a Circle!");
      

      is 一个圆圈,对吧?所以调用Circle.Draw() 应该是完全合乎逻辑的。

      【讨论】:

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