Какие в C# существуют способы расширения поведения класса?

Ответ

В C# есть несколько основных паттернов и языковых средств для расширения функциональности классов:

  1. Наследование (Inheritance) Классический способ создания иерархии "is-a". Новый класс наследует поля, свойства и методы базового, может их переопределять или добавлять новые.

    public class Vehicle { public void StartEngine() { } }
    public class Car : Vehicle { public void EnableAirConditioning() { } } // Car IS-A Vehicle
  2. Композиция (Composition) Принцип "has-a". Поведение расширяется за счёт включения экземпляров других классов и делегирования им вызовов. Это часто более гибкая альтернатива наследованию.

    public class Logger { public void Log(string msg) => Console.WriteLine(msg); }
    public class OrderProcessor {
        private readonly Logger _logger; // Композиция
        public OrderProcessor(Logger logger) { _logger = logger; }
        public void Process() { _logger.Log("Processing started..."); }
    }
  3. Методы расширения (Extension Methods) Позволяют добавлять методы к существующим типам (даже sealed или из сторонних библиотек) без изменения их исходного кода или создания подкласса.

    public static class DateTimeExtensions {
        public static bool IsWeekend(this DateTime date) => date.DayOfWeek == DayOfWeek.Saturday || date.DayOfWeek == DayOfWeek.Sunday;
    }
    // Использование:
    bool isWeekend = DateTime.Now.IsWeekend();
  4. Паттерн Декоратор (Decorator Pattern) Структурный паттерн, который динамически добавляет объекту новую функциональность, оборачивая его в другой объект с тем же интерфейсом.

    public interface INotifier { void Send(string message); }
    public class EmailNotifier : INotifier { public void Send(string msg) => Console.WriteLine($"Email: {msg}"); }
    public class SmsNotifierDecorator : INotifier {
        private readonly INotifier _wrappedNotifier;
        public SmsNotifierDecorator(INotifier notifier) { _wrappedNotifier = notifier; }
        public void Send(string msg) {
            _wrappedNotifier.Send(msg); // Базовая функциональность
            Console.WriteLine($"SMS: {msg}"); // Добавленная функциональность
        }
    }
  5. Частичные классы (Partial Classes) Позволяют разделить определение одного класса на несколько файлов. Часто используется в сгенерированном коде (например, WinForms, WPF), чтобы отделить пользовательскую логику от автоматически генерируемой.

    // Файл: MyClass.Generated.cs (автосгенерирован)
    public partial class MyClass { public int Id { get; set; } }
    // Файл: MyClass.Custom.cs (ручная разработка)
    public partial class MyClass { public string GetDisplayName() => $"ID: {Id}"; }

Выбор подхода зависит от контекста: наследование для иерархий типов, композиция для гибкости, методы расширения для утилитарных функций, декоратор для динамического добавления обязанностей.

Ответ 18+ 🔞

Давай разберём, как в C# можно натянуть новые фичи на старый класс, чтобы он не просто работал, а ещё и не развалился при этом. Способов — как говна за баней, выбирай любой.

Наследование (Inheritance) Это классика, типа «я твой папа». Берёшь готовый класс и делаешь его наследника, который всё от родителя получает, а ещё может своё добавить или что-то переписать. Работает по принципу «является» (is-a).

public class Vehicle { public void StartEngine() { } }
public class Car : Vehicle { public void EnableAirConditioning() { } } // Car — это Vehicle, только с кондишном

Но тут главное не переборщить, а то получишь иерархию глубже, чем проблемы у алкоголика, и потом сам в ней запутаешься.

Композиция (Composition) А это уже принцип «имеет» (has-a). Вместо того чтобы рожать наследников, ты просто берёшь и включаешь в свой класс другой класс, как деталь. И делегируешь ему работу. Гибко, модульно, и родительский класс тебе за это ничего не должен.

public class Logger { public void Log(string msg) => Console.WriteLine(msg); }
public class OrderProcessor {
    private readonly Logger _logger; // Вот она, композиция, просто поле
    public OrderProcessor(Logger logger) { _logger = logger; }
    public void Process() { _logger.Log("Начинаю процесс..."); } // Работу спихнул на логгер
}

Часто это надёжнее, чем наследование — меньше связанности, как будто не родственники, а просто соседи.

Методы расширения (Extension Methods) А это вообще магия, ебать! Хочешь добавить метод к классу, который тебе не принадлежит? К string, к DateTime, к чему угодно, даже к sealed? Пожалуйста! Пишешь статический метод с this перед первым параметром — и всё, теперь это будто бы родной метод класса. Никакого наследования, никакого доступа к приватным полям — чистая утилитарная подпорка.

public static class DateTimeExtensions {
    public static bool IsWeekend(this DateTime date) => date.DayOfWeek == DayOfWeek.Saturday || date.DayOfWeek == DayOfWeek.Sunday;
}
// Используешь как родной:
bool isWeekend = DateTime.Now.IsWeekend(); // И вроде бы у DateTime всегда был такой метод!

Идеально для всяких вспомогательных функций, чтобы не плодить хелперы по всему проекту.

Паттерн Декоратор (Decorator Pattern) Вот это уже для ценителей. Нужно динамически, в рантайме, навесить на объект новую функциональность? Оборачиваешь его в другой объект с тем же интерфейсом, как матрёшку. Внутри вызываешь обёрнутый объект, а вокруг своей логикой обрамляешь.

public interface INotifier { void Send(string message); }
public class EmailNotifier : INotifier { public void Send(string msg) => Console.WriteLine($"Email: {msg}"); }

public class SmsNotifierDecorator : INotifier {
    private readonly INotifier _wrappedNotifier;
    public SmsNotifierDecorator(INotifier notifier) { _wrappedNotifier = notifier; }
    public void Send(string msg) {
        _wrappedNotifier.Send(msg); // Сначала отправляем email (базовая фича)
        Console.WriteLine($"SMS: {msg}"); // А потом ещё и смс (дополнительная фича)
    }
}
// Использование:
INotifier notifier = new SmsNotifierDecorator(new EmailNotifier());
notifier.Send("Ты кто такой? Давай, до свидания!"); // Уйдёт и email, и смс

Красота в том, что оборачивать можно сколько угодно раз, хоть в десять слоёв, как луковицу.

Частичные классы (Partial Classes) А это специально для тех случаев, когда код класса генерируется автоматически (как в WinForms или WPF), а тебе нужно туда свою логику впихнуть. Класс делится на несколько файлов, и компилятор их склеивает в один. В одном файле — сгенерированный код, который трогать нельзя, а в другом — твои кастомные методы.

// Файл: MyClass.Generated.cs (сгенерирован тулзой, руки прочь)
public partial class MyClass { public int Id { get; set; } }

// Файл: MyClass.Custom.cs (тут твоё творчество)
public partial class MyClass { public string GetDisplayName() => $"ID: {Id}"; }

Удобно, когда не хочешь лезть в автогенерацию, но нужно класс дополнить.

Итог Какой способ выбрать? Да смотри по ситуации, блядь!

  • Наследование — когда есть чёткая иерархия «является».
  • Композиция — когда нужно собрать объект из частей, гибко и без тесных родственных связей.
  • Методы расширения — чтобы добавить утилитарный метод к чужому или системному типу.
  • Декоратор — когда нужно добавлять обязанности динамически, на лету.
  • Частичные классы — в основном для работы с автогенерируемым кодом.

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