Какие инструменты в C# используются для реализации полиморфизма?

«Какие инструменты в C# используются для реализации полиморфизма?» — вопрос из категории C# Core, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Полиморфизм ("много форм") в 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), а не компиляции. Следует использовать с осторожностью из-за потери безопасности типов и производительности.

Итог: Для проектирования гибких и расширяемых систем основными инструментами являются интерфейсы (для определения контрактов) и виртуальные/абстрактные методы (для предоставления расширяемого поведения в иерархии классов).