Полиморфизм: компиляция vs выполнение
Часть раздела Объектно-ориентированное программирование путешествия по C# на Coddy — урок 25 из 70.
Полиморфизм означает «многообразие форм» — способность одного и того же кода вести себя по-разному в зависимости от контекста. C# поддерживает два различных типа полиморфизма, и понимание того, когда разрешается каждый из них, помогает писать более гибкий код.
Полиморфизм на этапе компиляции (также называемый статическим полиморфизмом) разрешается компилятором до запуска вашей программы. Перегрузка методов является основным примером — несколько методов имеют одно и то же имя, но разные параметры:
public class Calculator
{
public int Add(int a, int b) => a + b;
public double Add(double a, double b) => a + b;
}
var calc = new Calculator();
calc.Add(5, 3); // Компилятор выбирает версию int
calc.Add(5.0, 3.0); // Компилятор выбирает версию doubleКомпилятор анализирует типы аргументов и выбирает соответствующий метод на этапе компиляции. Это решение фиксируется еще до того, как программа начнет выполняться.
Полиморфизм времени выполнения (динамический полиморфизм) разрешается во время выполнения программы. Это происходит с методами virtual и override — фактический тип объекта определяет, какой метод будет выполнен:
Animal pet = new Dog(); // Объявлен как Animal, фактически Dog
pet.Speak(); // Вызывает Speak() класса Dog во время выполненияКомпилятор не знает, какой именно Speak() будет запущен — это решение принимается во время выполнения на основе истинного типа объекта. Именно это делает полиморфизм на основе наследования таким мощным инструментом для создания гибких и расширяемых систем.
Задание
ЛегкоДавайте создадим систему конвертации, которая демонстрирует оба типа полиморфизма в действии. Вы создадите класс, использующий перегрузку методов (полиморфизм времени компиляции), наряду с иерархией наследования, использующей виртуальные/переопределенные методы (полиморфизм времени выполнения).
Вы организуете свой код в четырех файлах:
Converter.cs: Определите классConverterв пространстве именConversion. Этот класс демонстрирует полиморфизм времени компиляции через перегрузку методов. Создайте три перегруженных методаConvert:Convert(int value)возвращает"Integer: {value}"Convert(double value)возвращает"Double: {value}"Convert(string value)возвращает"String: {value}"
Shape.cs: Определите базовый классShapeв пространстве именConversion. Этот класс имеет свойствоName(string) и конструктор, который его устанавливает. Включитеvirtualметод с именемDescribe(), который возвращает"This is a {Name}".Circle.cs: Определите классCircleв пространстве именConversion, который наследуется отShape. Добавьте свойствоRadius(double). Конструктор принимает радиус и передает"Circle"в базовый конструктор. ПереопределитеDescribe(), чтобы он возвращал"This is a Circle with radius {Radius}".Program.cs: В основном файле продемонстрируйте оба типа полиморфизма. Сначала создайтеConverterи вызовите все три перегруженных метода с входными значениями. Затем создайтеCircle, сохраните его в переменной типаShapeи вызовитеDescribe(), чтобы показать полиморфизм времени выполнения.
Вы получите четыре входных значения:
- Целое число (integer)
- Число с плавающей точкой (double)
- Строковое значение (string)
- Радиус круга (double)
Выведите результат в следующем формате:
Compile-time Polymorphism:
{Convert(int) result}
{Convert(double) result}
{Convert(string) result}
Runtime Polymorphism:
{Describe() result from Shape variable holding Circle}Например, если входные данные — 42, 3.14, Hello и 5.5, вывод должен быть следующим:
Compile-time Polymorphism:
Integer: 42
Double: 3.14
String: Hello
Runtime Polymorphism:
This is a Circle with radius 5.5Обратите внимание на ключевое различие: компилятор решает, какой метод Convert вызвать, основываясь на типах аргументов во время компиляции, в то время как метод Describe() разрешается во время выполнения на основе фактического типа объекта, даже если он хранится в переменной Shape!
Шпаргалка
Полиморфизм означает «много форм» — способность одного и того же кода вести себя по-разному в зависимости от контекста.
Полиморфизм времени компиляции (статический полиморфизм) разрешается компилятором до запуска программы. Перегрузка методов является основным примером:
public class Calculator
{
public int Add(int a, int b) => a + b;
public double Add(double a, double b) => a + b;
}
var calc = new Calculator();
calc.Add(5, 3); // Компилятор выбирает версию int
calc.Add(5.0, 3.0); // Компилятор выбирает версию doubleКомпилятор проверяет типы аргументов и выбирает нужный метод во время компиляции.
Полиморфизм времени выполнения (динамический полиморфизм) разрешается во время работы программы с использованием методов virtual и override:
Animal pet = new Dog(); // Объявлено как Animal, на самом деле Dog
pet.Speak(); // Вызывает Speak() класса Dog во время выполненияФактический тип объекта определяет, какой метод будет выполнен во время выполнения, а не объявленный тип переменной.
Попробуйте сами
using System;
using Conversion;
class Program
{
public static void Main(string[] args)
{
// Чтение входных данных
int intValue = Convert.ToInt32(Console.ReadLine());
double doubleValue = Convert.ToDouble(Console.ReadLine());
string stringValue = Console.ReadLine();
double radius = Convert.ToDouble(Console.ReadLine());
// TODO: Продемонстрировать полиморфизм времени компиляции
// Создать объект Converter и вызвать все три перегруженных метода Convert
Console.WriteLine("Compile-time Polymorphism:");
// Вызвать Convert с intValue, doubleValue и stringValue
// TODO: Продемонстрировать полиморфизм времени выполнения
// Создать объект Circle и сохранить его в переменной типа Shape
// Затем вызвать Describe() для переменной Shape
Console.WriteLine("Runtime Polymorphism:");
// Создать Circle и вызвать Describe()
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыПространства имен и директивыВведение в классы и объектыКлючевое слово 'this'Методы и параметрыПоля и свойстваКонструкторыИнициализаторы объектовИтоги — Простой калькулятор4Наследование
Основы синтаксиса наследования (:)Ключевое слово 'base'Ключевые слова Virtual и OverrideЗапечатанные классыБазовый класс 'object'Итоги — Иерархия сотрудников7Продвинутые возможности
Перегрузка операторовИндексаторы (this[])Переопределение ToString()Методы расширенияПовторение — Пользовательский список10Паттерны проектирования. Часть 1
Введение в паттерны проектированияПотокобезопасный SingletonПаттерн FactoryПаттерн Observer (События)Паттерн Strategy13Финальные испытания
Платформа для онлайн-обученияКомпонент игрового персонажаСервис аренды транспорта2Свойства и статические члены
Автоматически реализуемые свойстваСвойства только для чтения и записиСтатические поля и методыСтатические классыЧлены в виде выражений5Полиморфизм и интерфейсы
Полиморфизм: компиляция vs выполнениеИнтерфейс vs абстрактный классМножественные интерфейсыЯвные интерфейсыUpcasting и DowncastingИтоги: Калькулятор фигур8Продвинутые концепции ООП
Композиция вместо наследованияGenerics (классы и методы)Делегаты и событияАтрибуты и рефлексияIDisposable и оператор usingОсновы Dependency Injection11Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн Компоновщик3Архитектура классов
Экземплярные и статические данныеКлючевые слова 'readonly' и 'const'Опорные поляПовторение — Менеджер банковских счетов6Инкапсуляция
Модификаторы доступаСвойства для инкапсуляцииРеализация скрытия данныхПаттерны неизменяемостиИтоги — Записи студентов9Переменное количество аргументов
Ключевое слово 'params'Необязательные параметрыИменованные аргументыПерегрузка методов