多态性和类型转换

12

我想要理解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 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
强制转换不会改变实际的实例 - 只是指向它的变量。 - Blorgbeard
2
+1 很棒的问题,赞你想要真正理解面向对象编程中发生了什么。 - BradleyDotNET
5个回答

13

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

请注意,以下两种情况是相同的示例:

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
@user3105717 3:检查Circle是否覆盖了Draw -> 是则调用它,否则 -> 去父类中检查...(实际上编译器/JIT能够提前解决大部分问题 - "调用对象的运行时类型中设置的Draw的任何实现")。有关内部工作的更多细节,请参见LordTakkera的答案 - 或在维基百科上查看VMT - Alexei Levenkov

8

如其他人所述,对象转换并不会改变实际实例。相反,转换允许变量承担从更高级别的对象继承的一部分特征。

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

//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()之外的任何东西。


6

你正在覆盖继承类中的方法,因此无论引用是指向不太具体的基类还是其他类型,该版本都将被调用。

如果要调用 Shape 中的版本,则需要一个类型为 Shape 的实例,而不仅仅是该类型的引用。


我同意你的第一句话,但我更关心为什么会这样。这种情况的内部机制是什么? - user3105717
@user3105717,我不想轻率地对此发表评论,但在审查我的想法后,我或许可以进行编辑。需要注意的一点是,您使用了一些Objective-C风格的措辞,这可能是您困惑的原因之一。C#并不像这样行事,我没有向“类”或其他东西发送“消息”。它更加静态。在编译时,编译器确定底层类型,找到该方法的定义并将其内联或引用。请不要将该解释视为事实,因为我需要进行一些审查才能给出完全准确的答案。 - evanmcdonnal

3
其他回答是完全正确的,但要尝试更深入地理解:多态性是通过使用称为虚函数指针表(vTable)的东西来实现的。实质上,你得到像下面一样的东西:
Shape -> Shape.Draw()
Circle -> Circle.Draw()
当你调用一个标记为“virtual”的函数时,编译器会执行 typeof,并调用该函数在类型继承树中属于最派生的实现。由于 Circle 继承自 Shape,并且你拥有一个 Circle 对象(如前所述,强制转换不影响底层类型),因此将调用 Circle.Draw。
显然,这是对实际发生的事情的过度简化,但希望它可以帮助解释为什么多态行为会表现出它的特征。

0

让我用"is-a"的术语来解释,因为这个形状是一个圆:

Shape shape = circle as Shape;

代码本身已经很清楚了,你将圆形称为“形状”,但这个形状并没有改变,它仍然是一个圆形,尽管它也是一种形状。
你甚至可以检查它是否是一个圆形:
if (shape is Circle)
    Console.WriteLine("The shape is a Circle!");

这是一个圆形,对吧?所以调用 Circle.Draw() 应该是完全合理的。


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