1. Stil der Deklaration und Benennung von Events
Events sind nicht einfach Delegates. Das ist eine eigene Entität zur Kommunikation zwischen Teilen der Anwendung, und ihre Deklaration sollte verständlich sein.
Verwende den richtigen Delegatetyp
In 99% der Fälle verwende die Standard-Delegates:
- EventHandler — für Events ohne Daten.
- EventHandler<TEventArgs> — wenn Parameter übergeben werden müssen.
Standardisierung erleichtert Wartung und Integration mit .NET-Bibliotheken. Erfinde keinen eigenen Delegate, wenn EventHandler passt.
public event EventHandler SomethingHappened; // Keine Daten
public event EventHandler<MyEventArgs> DataReceived; // Zusätzliche Daten vorhanden
Wenn spezielle Customization nötig ist — deklariere einen eigenen Delegate, aber das ist selten.
Namen von Events
In .NET werden Events im Perfekt benannt: Completed, Clicked, Changed, Received. Das betont, dass etwas bereits passiert ist.
Beispiele:
public event EventHandler DataLoaded; // Daten wurden geladen
public event EventHandler<MessageEventArgs> MessageReceived; // Nachricht empfangen
public event EventHandler Saving; // Der Speichervorgang hat begonnen
Manchmal wird die Form Changing für "vorher"-Events verwendet, um Eingreifen zu ermöglichen.
2. Organisation der Publisher-Klasse: protected virtual Methode OnEvent
Füge immer eine geschützte virtuelle Methode hinzu, die das Event auslöst: zentraler Aufrufpunkt, Erweiterbarkeit durch Vererbung und vorhersehbares Verhalten.
public class FileLoader
{
public event EventHandler<FileLoadedEventArgs> FileLoaded;
protected virtual void OnFileLoaded(FileLoadedEventArgs e)
{
FileLoaded?.Invoke(this, e);
}
public void Load(string filename)
{
// ... Logik zum Laden der Datei ...
OnFileLoaded(new FileLoadedEventArgs(filename));
}
}
public class FileLoadedEventArgs : EventArgs
{
public string FileName { get; }
public FileLoadedEventArgs(string fileName) => FileName = fileName;
}
Lass nur OnFileLoaded das Event auslösen — so ist es leichter zu warten und zu testen.
3. Regeln für Subscribe und Unsubscribe: Lebenszyklus, IDisposable
Wenn die Lebenszeit des Subscribers kürzer ist als die des Publishers, unsubscribe unbedingt vor der Zerstörung des Subscribers. Praktisch ist die Implementierung von IDisposable und das Abmelden in 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)
{
// Verarbeitung der Daten
}
public void Dispose()
{
_publisher.DataReceived -= HandleData;
}
}
// Verwendung mit using:
using (var listener = new TemporaryListener(myPublisher))
{
// listener hört hier Events
}
// Nach dem Verlassen des using - Dispose wurde aufgerufen, Unsubscribe ist erfolgt
Wenn man das Unsubscribe vergisst, hält der Publisher eine Referenz auf den Delegate des Subscribers — Memory Leak und "Zombie-Objekte" sind die Folge.
4. Thread-sicherer Aufruf von Events
In multithreaded Code können Subscriber parallel zum Auslösen des Events hinzugefügt/entfernt werden. Das führt zu Rennen und zu NullReferenceException. Verwende das threadsichere Pattern: kopiere den Delegate in eine lokale Variable.
protected virtual void OnSomethingHappened()
{
EventHandler handler = SomethingHappened;
handler?.Invoke(this, EventArgs.Empty);
}
Ab C# 6+ reicht:
SomethingHappened?.Invoke(this, EventArgs.Empty);
5. Verwende EventArgs statt object
Übergib keine Daten über object oder Klassenfelder. Nutze starke Typisierung durch Unterklassen von 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. Dokumentation von Events und Subscribern
Dokumentiere: wann das Event ausgelöst wird, Bedeutung der Felder in EventArgs, ob und wann ein Unsubscribe nötig ist.
/// <summary>
/// Das Event tritt nach erfolgreichem Laden der Daten auf.
/// </summary>
public event EventHandler<DataLoadedEventArgs> DataLoaded;
7. Zusammenfassende Empfehlungen zur Event-Architektur
Trenne Verantwortlichkeiten
Der Publisher benachrichtigt nur über das Ereignis. Der Subscriber entscheidet selbst, wann er subscribe/ unsubscribe.
Vermeide "Spam" von Events
Erzeuge nicht dasselbe Event dutzende Male pro Sekunde ohne Grund — das belastet unnötig.
Vermeide Events für bidirektionale Kommunikation
Events sind für "one-to-many" geeignet. Für bidirektionale Kommunikation erwäge Interfaces, Callbacks oder andere Mechanismen.
Halte keine direkten Referenzen auf Subscriber in der Klasse
Behalte keine expliziten Referenzen auf Subscriber — Events und Delegates übernehmen das automatisch.
8. Klassische Antipatterns
Typlose Events
public event Action<object> SomethingHappened; // Unklar, was drinsteckt
Schlecht: Typensicherheit ist gebrochen, man braucht Casts, Wartbarkeit leidet.
Unsubscribe vergessen
public class ShortLivedListener
{
public ShortLivedListener(Publisher p) =>
p.DataReceived += DoWork;
private void DoWork(object sender, EventArgs e) { /* ... */ }
// Kein Dispose, kein Unsubscribe => Zombie-Objekte!
}
Verletzung des SRP
Eine Klasse ist gleichzeitig Publisher, Subscriber und Handler — Rollen vermischen. Trenne Verantwortlichkeiten.
9. Praktische Anwendung in Interviews und Projekten
In vielen Projekten mit Publish-Subscribe ist eine saubere Event-Organisation entscheidend für Skalierbarkeit und Wartbarkeit. In Interviews wird oft verlangt:
- ein Event-System mit korrekter Typisierung zu implementieren,
- das Management des Lebenszyklus von Subscribers zu demonstrieren,
- den threadsicheren Aufruf von Events zu erklären.
Sauberer, dokumentierter, korrekt organisierter Event-Code hebt dich bei Bewerbungen hervor.
GO TO FULL VERSION