JavaRush /Курсы /C# SELF /Рекомендации по организации кода

Рекомендации по организации кода

C# SELF
54 уровень , 1 лекция
Открыта

1. Стиль объявления и именования событий

События — не просто делегаты. Это отдельная сущность для коммуникации между частями приложения, и её объявление должно быть понятным.

Используйте правильный тип делегата

В 99% случаев используйте стандартные делегаты:

  • EventHandler — для событий без данных.
  • EventHandler<TEventArgs> — когда нужно передать параметры.

Стандартизация облегчает поддержку кода и интеграцию с библиотеками .NET. Не изобретайте свой делегат, если подходит EventHandler.


public event EventHandler SomethingHappened; // Нет данных
public event EventHandler<MyEventArgs> DataReceived; // Есть дополнительные данные

Если нужна особая кастомизация — объявляйте собственный делегат, но это редкость.

Именование событий

В .NET события называют в прошедшем времени: Completed, Clicked, Changed, Received. Это подчёркивает факт произошедшего.

Примеры:


public event EventHandler DataLoaded;    // Данные были загружены
public event EventHandler<MessageEventArgs> MessageReceived; // Сообщение получено
public event EventHandler Saving;        // Начался процесс сохранения

Иногда используют форму Changing для событий "до" изменения, чтобы дать шанс вмешаться.

2. Организация класса-издателя: виртуальный метод OnEvent

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


public class FileLoader
{
    public event EventHandler<FileLoadedEventArgs> FileLoaded;

    protected virtual void OnFileLoaded(FileLoadedEventArgs e)
    {
        FileLoaded?.Invoke(this, e);
    }

    public void Load(string filename)
    {
        // ... логика загрузки файла ...
        OnFileLoaded(new FileLoadedEventArgs(filename));
    }
}

public class FileLoadedEventArgs : EventArgs
{
    public string FileName { get; }
    public FileLoadedEventArgs(string fileName) => FileName = fileName;
}

Пусть только OnFileLoaded вызывает событие — так проще сопровождать и тестировать.

3. Правила подписки и отписки: жизненный цикл, IDisposable

Если срок жизни подписчика меньше, чем у издателя, обязательно отпишитесь до уничтожения подписчика. Удобно реализовать IDisposable и отписываться в Dispose().


public class TemporaryListener : IDisposable
{
    private readonly Publisher _publisher;

    public TemporaryListener(Publisher publisher)
    {
        _publisher = publisher;
        _publisher.DataReceived += HandleData;
    }

    private void HandleData(object sender, EventArgs e)
    {
        // Работа с данными
    }

    public void Dispose()
    {
        _publisher.DataReceived -= HandleData;
    }
}

// Использование с using:
using (var listener = new TemporaryListener(myPublisher))
{
    // listener слушает события здесь
}
// После выхода из using - Dispose вызван, отписка произошла

Если забыть отписаться, издатель удержит ссылку на делегат подписчика — получите утечку памяти и "зомби-объекты".

4. Потокобезопасный вызов событий

В многопоточном коде подписчики могут добавляться/удаляться параллельно с вызовом события. Это чревато гонками и NullReferenceException. Используйте потокобезопасный шаблон: копируйте делегат в локальную переменную.


protected virtual void OnSomethingHappened()
{
    EventHandler handler = SomethingHappened;
    handler?.Invoke(this, EventArgs.Empty);
}

С C# 6+ достаточно:


SomethingHappened?.Invoke(this, EventArgs.Empty);

5. Использование EventArgs вместо object

Не передавайте данные через object и поля класса. Используйте строгую типизацию через наследников EventArgs.


public class DownloadCompletedEventArgs : EventArgs
{
    public string FileName { get; }
    public long Size { get; }
    public DownloadCompletedEventArgs(string fileName, long size)
    {
        FileName = fileName;
        Size = size;
    }
}

public event EventHandler<DownloadCompletedEventArgs> DownloadCompleted;

6. Документирование событий и подписчиков

Документируйте: когда вызывается событие, смысл полей EventArgs, нужен ли отписка и когда.


/// <summary>
/// Событие возникает после успешной загрузки данных.
/// </summary>
public event EventHandler<DataLoadedEventArgs> DataLoaded;

7. Сводные рекомендации по архитектуре событий

Разделяйте обязанности

Издатель только оповещает о факте. Подписчик сам решает, когда подписаться и отписаться.

Избегайте "бомбёжки" событиями

Не генерируйте одно и то же событие десятки раз в секунду без надобности — это избыточная нагрузка.

Старайтесь не использовать события для двусторонней связи

События — для схемы "один сообщает — много слушают". Для двусторонней коммуникации рассмотрите интерфейсы, коллбеки или другие механизмы.

Не храните в классе подписчиков

Не удерживайте явные ссылки на подписчиков — события и делегаты сделают это автоматически.

8. Классические антипаттерны

Безтиповые события


public event Action<object> SomethingHappened; // Не ясно, что там внутри

Плохо: сломана типизация, нужны касты, теряется поддерживаемость.

Забыли про отписку


public class ShortLivedListener
{
    public ShortLivedListener(Publisher p) =>
        p.DataReceived += DoWork;

    private void DoWork(object sender, EventArgs e) { /* ... */ }
    // Нет Dispose, нет отписки => зомби-объекты!
}

Нарушение SRP

Класс одновременно и издатель, и подписчик, и обработчик — смешение ролей. Разделяйте ответственность.

9. Практическое применение на собеседованиях и в проектах

Во многих проектах с публикацией-подпиской грамотная организация событий — залог масштабируемости и поддержки. На собеседованиях нередко просят:

  • реализовать систему событий с правильной типизацией,
  • показать управление жизненным циклом подписчиков,
  • объяснить потокобезопасный вызов событий.

Чистый, документированный, корректно организованный событийный код сразу выделяет вас среди кандидатов.

2
Задача
C# SELF, 54 уровень, 1 лекция
Недоступна
Организация событий с несколькими подписчиками
Организация событий с несколькими подписчиками
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ