CodeGym /Kursy /C# SELF /Rekomendacje dotyczące organizacji kodu

Rekomendacje dotyczące organizacji kodu

C# SELF
Poziom 54 , Lekcja 1
Dostępny

1. Styl deklarowania i nazywania wydarzeń

Wydarzenia — to nie tylko delegaty. To odrębna jednostka komunikacji między częściami aplikacji i jej deklaracja powinna być czytelna.

Używaj właściwego typu delegata

W 99% przypadków używaj standardowych delegatów:

  • EventHandler — dla wydarzeń bez danych.
  • EventHandler<TEventArgs> — gdy trzeba przekazać parametry.

Standaryzacja ułatwia utrzymanie kodu i integrację z bibliotekami .NET. Nie wymyślaj własnego delegata, jeśli pasuje EventHandler.


public event EventHandler SomethingHappened; // Brak danych
public event EventHandler<MyEventArgs> DataReceived; // Są dodatkowe dane

Jeśli potrzebujesz specjalnej customizacji — zadeklaruj własny delegat, ale to rzadkość.

Nazewnictwo wydarzeń

W .NET wydarzenia nazywa się w czasie przeszłym: Completed, Clicked, Changed, Received. Podkreśla to fakt, że coś się wydarzyło.

Przykłady:


public event EventHandler DataLoaded;    // Dane zostały załadowane
public event EventHandler<MessageEventArgs> MessageReceived; // Wiadomość odebrana
public event EventHandler Saving;        // Rozpoczęto proces zapisu

Czasem używa się formy Changing dla wydarzeń "przed" zmianą, żeby dać możliwość ingerencji.

2. Organizacja klasy-wydawcy: chroniona metoda wirtualna OnEvent

Zawsze dodawaj chronioną wirtualną metodę, która wywołuje wydarzenie: scentralizowany punkt wywołań, rozszerzalność przy dziedziczeniu i przewidywalność zachowania.


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

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

    public void Load(string filename)
    {
        // ... logika ładowania pliku ...
        OnFileLoaded(new FileLoadedEventArgs(filename));
    }
}

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

Niech tylko OnFileLoaded wywołuje wydarzenie — łatwiej wtedy utrzymywać i testować.

3. Zasady subskrypcji i odsubskrypcji: cykl życia, IDisposable

Jeśli czas życia subskrybenta jest krótszy niż wydawcy, koniecznie odsubskrybuj przed zniszczeniem subskrybenta. Wygodnie zaimplementować IDisposable i odsubskrybować w 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)
    {
        // Praca z danymi
    }

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

// Użycie z using:
using (var listener = new TemporaryListener(myPublisher))
{
    // listener nasłuchuje wydarzeń tutaj
}
// Po wyjściu z using - Dispose wywołany, odsubskrybowano

Jeśli zapomnisz odsubskrybować, wydawca utrzyma referencję do delegata subskrybenta — dostaniesz wyciek pamięci i "zombie-obyekty".

4. Bezpieczeństwo wątkowe wywołań wydarzeń

W wielowątkowym kodzie subskrybenci mogą być dodawani/usuwani równolegle z wywołaniem wydarzenia. To grozi wyścigami i NullReferenceException. Użyj bezpiecznego wzorca: skopiuj delegat do lokalnej zmiennej.


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

W C# 6+ wystarczy:


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

5. Używaj EventArgs zamiast object

Nie przekazuj danych przez object ani pola klasy. Używaj silnego typowania przez dziedziczące 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. Dokumentowanie wydarzeń i subskrybentów

Dokumentuj: kiedy wydarzenie jest wywoływane, znaczenie pól EventArgs, czy i kiedy trzeba odsubskrybować.


/// <summary>
/// Wydarzenie występuje po pomyślnym załadowaniu danych.
/// </summary>
public event EventHandler<DataLoadedEventArgs> DataLoaded;

7. Zbiorcze rekomendacje dotyczące architektury wydarzeń

Rozdzielaj obowiązki

Wydawca tylko informuje o zdarzeniu. Subskrybent sam decyduje, kiedy subskrybować i odsubskrybować.

Unikaj "bombardowania" wydarzeniami

Nie generuj tego samego wydarzenia dziesiątki razy na sekundę bez potrzeby — to nadmierne obciążenie.

Stosuj wydarzenia raczej nie do komunikacji dwukierunkowej

Wydarzenia są do schematu "jeden raportuje — wielu nasłuchuje". Dla komunikacji dwukierunkowej rozważ interfejsy, callbacki lub inne mechanizmy.

Nie przechowuj w klasie subskrybentów

Nie trzymaj jawnych referencji do subskrybentów — wydarzenia i delegaty zrobią to automatycznie.

8. Klasyczne antywzorce

Wydarzenia bez typów


public event Action<object> SomethingHappened; // Nie wiadomo, co jest w środku

Słabo: łamie się typowanie, potrzebne są rzutowania, traci się utrzymywalność.

Zapomniano o odsubskrybowaniu


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

    private void DoWork(object sender, EventArgs e) { /* ... */ }
    // Brak Dispose, brak odsubskrybowania => zombie-obyekty!
}

Łamanie SRP

Klasa jednocześnie wydawca, subskrybent i handler — mieszanie ról. Rozdziel odpowiedzialności.

9. Praktyczne zastosowanie na rozmowach kwalifikacyjnych i w projektach

W wielu projektach z publikacją-subskrypcją poprawna organizacja wydarzeń to klucz do skalowalności i utrzymania. Na rozmowach technicznych często proszą o:

  • zaimplementowanie systemu wydarzeń z poprawnym typowaniem,
  • pokazanie zarządzania cyklem życia subskrybentów,
  • wyjaśnienie bezpiecznego wątkowo wywoływania wydarzeń.

Czysty, udokumentowany, poprawnie zorganizowany kod oparty na wydarzeniach od razu wyróżnia kandydata.

2
Zadanie
C# SELF, poziom 54, lekcja 1
Niedostępne
Organizacja zdarzeń z wieloma subskrybentami
Organizacja zdarzeń z wieloma subskrybentami
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION