Полиморфизм и литье

Я хочу понять полиморфизм в С#, поэтому, попробовав несколько конструкций, я придумал следующий пример:

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

Кастинг не изменяет тип времени выполнения и какую реализацию конкретного виртуального метода у каждого экземпляра.

Обратите внимание, что следующие два случая, которые вы имеете в качестве образца, идентичны:

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

и

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

Первая - по существу более короткая версия второй.

Ответ 2

Как отмечали другие, бросание объекта не изменяет фактический экземпляр; вместо этого кастинг позволяет переменной принимать подмножество характеристик экземпляра из более высокого уровня в иерархии объектов.

Чтобы продемонстрировать, зачем это нужно, рассмотрите этот пример:

//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();
}

Вызов Draw() в цикле foreach вызовет метод Draw() производного экземпляра, то есть Square.Draw() и Circle.Draw(). Это позволяет в этом примере рисовать каждую отдельную фигуру, не зная точно, какую фигуру вы рисуете во время выполнения. Вы просто знаете, что вам нужна форма, пусть форма обрабатывается, как она нарисована.

Если это не так (и это касается наследования на других языках, а не только на С#), вы не сможете использовать ничего, кроме Shape.Draw().

Ответ 3

Вы переопределяете метод в классе наследования, поэтому всегда будет отображаться версия, независимо от того, ссылается ли ваша ссылка на менее конкретный базовый класс.

Если вы хотите вызвать версию в Shape, вам нужен экземпляр типа Shape, а не только ссылка этого типа.

Ответ 4

Другие ответы абсолютно правильны, но попытаться идти на один уровень глубже:

Полиморфизм реализуется посредством использования так называемой таблицы указателей виртуальной функции (vTable). По сути, вы получаете что-то вроде:

Форма → Shape.Draw()

Круг → Circle.Draw()

Когда вы вызываете функцию с пометкой "virtual", компилятор делает typeof и вызывает реализацию этой самой производной этой функции, которая является частью дерева наследования типов. Поскольку Circle наследуется от Shape, и у вас есть объект Circle (как отмечено ранее, casting не влияет на базовый тип), вызывается Circle.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() должен быть совершенно логичным.