Ответ
Полиморфизм ("много форм") в C# — это возможность объектов производных классов обрабатываться как объекты базового класса, но при этом вызывать свои собственные реализации методов. Это краеугольный камень ООП, достигаемый через несколько механизмов.
1. Наследование и виртуальные методы (Переопределение / Overriding)
Ключевые слова: virtual, override.
Суть: Базовый класс объявляет метод как virtual, разрешая наследникам предоставить свою специфичную реализацию через override.
public class PaymentProcessor
{
// Виртуальный метод с реализацией по умолчанию
public virtual void ProcessPayment(decimal amount)
{
Console.WriteLine($"Processing base payment of {amount}");
// Общая логика (логирование, валидация суммы)
}
}
public class CreditCardProcessor : PaymentProcessor
{
// Переопределение с добавлением специфичной логики
public override void ProcessPayment(decimal amount)
{
// Можно вызвать базовую реализацию, если нужно
base.ProcessPayment(amount);
Console.WriteLine($"Charging {amount} to credit card.");
// Логика связи с платежным шлюзом
}
}
// Использование
PaymentProcessor processor = new CreditCardProcessor();
processor.ProcessPayment(100.00m); // Вызовется CreditCardProcessor.ProcessPayment
Почему это важно: Позволяет расширять поведение базового класса, не нарушая его контракт и используя общий интерфейс.
2. Абстрактные классы и методы
Ключевые слова: abstract.
Суть: Абстрактный класс не может быть инстанциирован. Он определяет контракт в виде абстрактных методов (без реализации), которые обязаны быть реализованы в конкретных наследниках.
public abstract class Shape
{
public abstract double CalculateArea(); // Контракт: у любой фигуры есть площадь
public string Name { get; set; } // Абстрактный класс может иметь обычные члены
public void PrintDescription() => Console.WriteLine($"Shape: {Name}");
}
public class Circle : Shape
{
public double Radius { get; set; }
public override double CalculateArea() => Math.PI * Radius * Radius; // Обязательная реализация
}
// Использование
Shape shape = new Circle { Radius = 5 };
Console.WriteLine(shape.CalculateArea()); // 78.54...
Когда использовать: Когда у группы классов есть общая, четко определенная структура и часть поведения, но ключевые операции уникальны для каждого.
3. Интерфейсы
Ключевое слово: interface.
Суть: Интерфейс — это чистый контракт, определяющий только сигнатуры методов, свойств, событий или индексаторов. Класс может реализовывать множество интерфейсов.
public interface ILoggable
{
string GetLogMessage(); // Только сигнатура, нет реализации
}
public interface ISerializable
{
string SerializeToJson();
}
public class Order : ILoggable, ISerializable // Множественное "наследование"
{
public int Id { get; set; }
public string GetLogMessage() => $"Order {Id} processed."; // Реализация контракта ILoggable
public string SerializeToJson() => JsonSerializer.Serialize(this); // Реализация контракта ISerializable
}
// Использование
ILoggable loggableItem = new Order { Id = 1 };
Console.WriteLine(loggableItem.GetLogMessage());
Почему интерфейсы предпочтительнее для полиморфизма: Они обеспечивают более слабую связность (loose coupling), позволяют множественное наследование и идеально подходят для Dependency Injection и тестирования.
4. Перегрузка методов (Overloading) — Ad-hoc полиморфизм
Суть: Несколько методов в одном классе с одинаковым именем, но разными параметрами (по типу или количеству). Компилятор выбирает подходящий метод на этапе компиляции.
public class Calculator
{
public int Add(int a, int b) => a + b;
public double Add(double a, double b) => a + b; // Перегрузка
public int Add(int a, int b, int c) => a + b + c; // Еще одна перегрузка
}
5. Дополнительные механизмы
- Ковариантность и контравариантность (в делегатах и обобщенных интерфейсах): Позволяют использовать более производные (
out) или более базовые (in) типы, чем задано изначально.IEnumerable<string> strings = new List<string>(); IEnumerable<object> objects = strings; // Ковариантность: string → object (допустимо благодаря 'out' в IEnumerable<out T>) - Динамический полиморфизм (
dynamic): Разрешение типа и вызов метода происходит во время выполнения (runtime), а не компиляции. Следует использовать с осторожностью из-за потери безопасности типов и производительности.
Итог: Для проектирования гибких и расширяемых систем основными инструментами являются интерфейсы (для определения контрактов) и виртуальные/абстрактные методы (для предоставления расширяемого поведения в иерархии классов).