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. Практичне застосування на співбесідах і в проєктах

У багатьох проєктах із моделлю publish/subscribe грамотна організація подій — запорука масштабованості та підтримуваності. На співбесідах нерідко просять:

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

Чистий, задокументований і коректно організований подієвий код одразу вирізняє вас серед кандидатів.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ