Язык программирования C#9 и платформа .NET5
}// В классе Hexagon метод Draw() переопределяется.class Hexagon : Shape{public Hexagon() {}public Hexagon(string name) : base(name){}public override void Draw(){Console.WriteLine("Drawing {0} the Hexagon", PetName);}}Полезность абстрактных методов становится совершенно ясной, как только вы снова вспомните, что подклассы никогда не обязаны переопределять виртуальные методы (как в случае
). Следовательно, если создать экземпляры типовCircleиHexagon, то обнаружится, чтоCircleзнает, как правильно "рисовать" себя (или, по крайней мере, выводить на консоль подходящее сообщение). Тем не менее, реакцияHexagonпорядком сбивает с толку.CircleConsole.WriteLine("***** Fun with Polymorphism *****\n");Hexagon hex = new Hexagon("Beth");hex.Draw();Circle cir = new Circle("Cindy");// Вызывает реализацию базового класса!cir.Draw();Console.ReadLine();Взгляните на вывод предыдущего кода:
***** Fun with Polymorphism *****Drawing Beth the HexagonInside Shape.Draw()Очевидно, что это не самое разумное проектное решение для текущей иерархии. Чтобы вынудить каждый дочерний класс переопределять метод
, его можно определить как абстрактный метод классаDraw(), т.е. какая-либо стандартная реализация вообще не предлагается. Для пометки метода как абстрактного в C# используется ключевое словоShape. Обратите внимание, что абстрактные методы не предоставляют никакой реализации:abstractabstract class Shape{// Вынудить все дочерние классы определять способ своей визуализации.public abstract void Draw();...}На заметку! Абстрактные методы могут быть определены только в абстрактных классах, иначе возникнет ошибка на этапе компиляции.
Методы, помеченные как
t, являются чистым протоколом. Они просто определяют имя, возвращаемый тип (если есть) и набор параметров (при необходимости). Здесь абстрактный классabstracинформирует производные типы о том, что у него есть метод по имениShape, который не принимает аргументов и ничего не возвращает. О необходимых деталях должен позаботиться производный класс.Draw()С учетом сказанного метод
в классеDraw()теперь должен быть обязательно переопределен. В противном случаеCircleтакже должен быть абстрактным классом и декорироваться ключевым словомCircle(что очевидно не подходит в настоящем примере). Вот изменения в коде:abstract// Если не реализовать здесь абстрактный метод Draw(), то Circle// также должен считаться абстрактным и быть помечен как abstract!class Circle : Shape{public Circle() {}public Circle(string name) : base(name) {}public override void Draw(){Console.WriteLine("Drawing {0} the Circle", PetName);}}Итак, теперь можно предполагать, что любой класс, производный от
, действительно имеет уникальную версию методаShape. Для демонстрации полной картины полиморфизма рассмотрим следующий код:Draw()Console.WriteLine("***** Fun with Polymorphism *****\n");// Создать массив совместимых с Shape объектов.Shape[] myShapes = {new Hexagon(), new Circle(), new Hexagon("Mick"),new Circle("Beth"), new Hexagon("Linda")};// Пройти в цикле по всем элементам и взаимодействовать// с полиморфным интерфейсом.foreach (Shape s in myShapes){s.Draw();}Console.ReadLine();Ниже показан вывод, выдаваемый этим кодом:
***** Fun with Polymorphism *****Drawing NoName the HexagonDrawing NoName the CircleDrawing Mick the HexagonDrawing Beth the CircleDrawing Linda the HexagonДанный код иллюстрирует полиморфизм в чистом виде. Хотя напрямую создавать экземпляры абстрактного базового класса (
) невозможно, с помощью абстрактной базовой переменной допускается хранить ссылки на объекты любого подкласса. Таким образом, созданный массив объектовShapeспособен хранить объекты классов, производных от базового классаShape(попытка добавления в массив объектов, несовместимых сShape, приведет к ошибке на этапе компиляции).Shape