CodeGym /Kurse /C# SELF /Praktische Szenarien und Best Practices

Praktische Szenarien und Best Practices

C# SELF
Level 53 , Lektion 4
Verfügbar

1. Praktische Szenarien für Events

Events sind nicht nur schöne Theorie aus Lehrbüchern. Praktisch werden sie in fast jeder zweiten Anwendung benötigt, und wenn du mit UI oder Netzwerksdiensten arbeitest — dann in den meisten Fällen.

Lass uns einige typische Szenarien aus der realen Entwicklung anschauen. Gleichzeitig sehen wir, wie Standards und Best Practices helfen, häufige Fehler zu vermeiden.

Reagieren auf Nutzeraktionen (wie im UI)

Wahrscheinlich das häufigste Szenario — wenn der Nutzer etwas macht (drückt einen Button, wählt ein Element in einer Liste), und die Anwendung muss reagieren. Genau so funktionieren Windows Forms, WPF, UWP, Avalonia, MAUI und andere UI-Frameworks.

Kurzbeispiel (ohne UI — wir simulieren einen Button):


public class Button
{
    public event EventHandler? Click; // standard delegate

    public void SimulateClick()
    {
        Click?.Invoke(this, EventArgs.Empty); // "Button" wurde "geklickt"
    }
}

class Program
{
    static void Main()
    {
        var button = new Button();
        button.Click += (sender, e) => Console.WriteLine("Button gedrückt!");
        button.SimulateClick(); // > Button gedrückt!
    }
}

In einem echten UI-Framework feuert das Event Click, wenn der Nutzer mit der Maus klickt oder auf dem Bildschirm tippt. Alles, was auf dieses Event subscribed ist, bekommt die Benachrichtigung — so läuft die ganze Anwendungslogik.

Signalisierung von Fortschritt, Abschluss von Operationen und Fehlern

Stell dir vor: Dateidownload, Datenverarbeitung, lange Berechnung. Man muss über Fortschritt oder Fehler informieren. Oft gibt es ein Event für "Fortschrittsschritt" und separate Events für "abgeschlossen" oder "Fehler".

Beispiel eines Downloaders:


public class FileDownloader
{
    public event EventHandler<int>? ProgressChanged;
    public event EventHandler? DownloadCompleted;
    public event EventHandler<string>? DownloadFailed;

    public void Download()
    {
        for (int i = 1; i <= 100; i += 10)
        {
            Thread.Sleep(50); // Verzögerung simulieren
            ProgressChanged?.Invoke(this, i);
        }
        DownloadCompleted?.Invoke(this, EventArgs.Empty);
    }
}

var downloader = new FileDownloader();
downloader.ProgressChanged += (s, progress) => Console.WriteLine($"Download: {progress}%");
downloader.DownloadCompleted += (s, e) => Console.WriteLine("Download beendet.");
downloader.Download();

Hier gibt es mehrere Events: man kann auf alles subscriben (oder nur auf das Nötige). Das ähnelt dem Verhalten vieler Standard-.NET‑Klassen für Threads, File-Download, HTTP usw.

Reaktion auf Lebenszyklus von Objekten (z. B. "gespeichert", "gelöscht")

Angenommen, du hast ein Domain-Modell. Du möchtest, dass wenn ein Nutzer ein Objekt speichert oder löscht, einige Prozesse reagieren: Cache aktualisieren, Logging, Nachrichten senden usw.


public class UserRepository
{
    public event EventHandler<UserEventArgs>? UserSaved;
    public event EventHandler<UserEventArgs>? UserDeleted;

    public void Save(User user)
    {
        //... Nutzer speichern
        UserSaved?.Invoke(this, new UserEventArgs(user));
    }

    public void Delete(User user)
    {
        //... Nutzer löschen
        UserDeleted?.Invoke(this, new UserEventArgs(user));
    }
}

public class UserEventArgs : EventArgs
{
    public User User { get; }
    public UserEventArgs(User user) => User = user;
}

Jetzt können verschiedene Module subscriben und — ohne direkte Kopplung! — Benachrichtigungen über Änderungen an Nutzern erhalten. Der Repository selbst weiß nicht, wer "angeschlossen" ist.

Asynchrone Events und Multithreading

Manchmal werden Events nicht aus dem Haupt(UI)-Thread generiert — z. B. aus Hintergrundtasks, Timern oder asynchronen Operationen. In solchen Fällen ist wichtig zu beachten, dass der Code der Handler in einem "fremden" Thread ausgeführt wird. Wenn ein Handler versucht, UI-Elemente direkt aus einem anderen Thread zu aktualisieren, führt das zu Fehlern.

Was ist Marshalling? Marshalling ist das Verschieben der Ausführung von Code von einem Thread in einen anderen, normalerweise vom Hintergrundthread zurück in den UI-Thread, um das UI sicher zu aktualisieren. In UI-Anwendungen (WinForms, WPF) werden Mechanismen wie SynchronizationContext oder Dispatcher verwendet, die erlauben, den Aufruf des Handlers auf den richtigen Thread "umzuleiten".

Mach das nicht:


// Event wird aus einem Hintergrundthread aufgerufen und der Handler aktualisiert UI direkt — du bekommst eine Exception!

Empfehlung: prüfe den Ausführungs-Thread und mache bei Bedarf Marshalling auf den UI-Thread (via SynchronizationContext oder Dispatcher). In Konsolen- und Serveranwendungen gibt es solche Einschränkungen meist nicht.

Event Aggregator / Messaging

In großen Anwendungen verwendet man oft einen zentralen Event-Aggregator. Er reduziert Kopplung: Subscriber und Publisher kennen einander nicht und tauschen Nachrichten über einen Hub aus.


public class EventAggregator
{
    public event EventHandler<SomeEventArgs>? SomeEvent;

    public void Publish(SomeEventArgs args)
    {
        SomeEvent?.Invoke(this, args);
    }
}

2. Beste Patterns und Praktiken

Nutze das Attribut [CallerMemberName] für Fehler

Wenn du Infrastruktur-Code schreibst (z. B. Logger, Tracer), logge den Namen der aufrufenden Methode mit dem Attribut CallerMemberName — das erleichtert das Debugging.

Mach Events thread-safe, wenn nötig

Events sind multicast, und Aufrufe aus verschiedenen Threads erfordern Vorsicht: bevor du aufrufst, kopiere den Delegate in eine lokale Variable.


var handler = MyEvent;
if (handler != null) handler(this, args);

In C# 6+ nutze den sicheren Aufruf: MyEvent?.Invoke(this, args).

Gestalte benutzerdefinierte EventArgs immutable

Definiere deine eigenen EventArgs-Typen mit readonly-Eigenschaften — das verhindert unbeabsichtigte Änderungen des Event-Zustands durch Subscriber.


public class WorkCompletedEventArgs : EventArgs
{
    public string Message { get; }
    public WorkCompletedEventArgs(string message) => Message = message;
}

public oder private event

Mache Events public event, und kapsle den Aufruf in einer protected virtual-Methode OnEventName. Das ermöglicht Erweiterbarkeit (ein Subclass kann das Verhalten überschreiben), und externe Konsumenten können das Event nicht direkt feuern.

Verletze nicht die Reihenfolge des Lebenszyklus

Wenn du eine lange Kette von Handlern startest, denk daran: jemand könnte sich subscribe und versuchen, die interne Logik zu ändern. Dokumentiere, wo und wann Events aufgerufen werden und ob ein Event mehrfach feuern kann.

Mehrere Events und Komposition

Wenn ein Objekt mehrere Quellen beobachtet, gruppiere Subscription und Unsubscription an einem Ort (z. B. Methoden Subscribe/Unsubscribe). Falls nötig, halte private Delegate-Felder für einfaches Unsubscribing.

3. Wie man Events NICHT verwenden sollte: typische Fehler

Fehler Nr.1: Ignorieren des Standard-Event-Patterns.
Wenn jede Klasse eigene Delegates für jedes Event definiert, wird das schnell chaotisch. Versuche, die Standard-Delegates EventHandler oder EventHandler<T> zu verwenden, wobei T von EventArgs erbt. Das macht den Code verständlicher und vertrauter für .NET-Entwickler.

Fehler Nr.2: Event fälschlich von externem Code aufrufen.
Rufe niemals ein Event direkt außerhalb des Publisher-Klasse auf. Ein Event wird nur innerhalb der Klasse initiiert, üblicherweise via einer geschützten Methode OnEventName. Subscriber dürfen nur subscriben (+=) und unsubscriben (-=).

Schlecht:


foo.MyEvent(); // Fehler! Du darfst ein Event nicht direkt von außen aufrufen

Gut:


// Nur innerhalb von foo:
// protected virtual void OnMyEvent()
// {
//     MyEvent?.Invoke(this, EventArgs.Empty);
// }

Fehler Nr.3: Event ohne Prüfung auf Subscriber (null) aufrufen.
Wenn du versuchst ein Event aufzurufen, das keine Subscriber hat, bekommst du eine NullReferenceException. In C# 6.0+ nutze ?.Invoke(...) — das Event wird nur aufgerufen, wenn Subscriber vorhanden sind.

Fehler Nr.4: Vergessen, sich von Events abzumelden (Memory Leak).
Wenn ein Objekt sich für ein Event anmeldet, aber sich nicht abmeldet bevor es zerstört wird, hält der Publisher eine Referenz auf den Subscriber. Der Garbage Collector kann das Objekt nicht freigeben — das ist kritisch bei long-lived Publishern und kurzlebigen Subscribern (z. B. ViewModel, Form). Beste Lösung: explizites Unsubscribe oder Verwendung von WeakReference dort, wo es passt.

Fehler Nr.5: Event außerhalb einer geschützten virtuellen Methode OnEvent aufrufen.
Wenn du das Event direkt aufrufst, nimmst du den Erben die Möglichkeit, das Verhalten korrekt zu überschreiben. Das richtige Pattern ist die Deklaration einer protected virtual-Methode, die das Event aufruft.

Standardbeispiel:


protected virtual void OnSomething(EventArgs e)
{
    Something?.Invoke(this, e);
}
1
Umfrage/Quiz
Lebenszyklus von Events, Level 53, Lektion 4
Nicht verfügbar
Lebenszyklus von Events
Detaillierte Analyse der Event-Subscription
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION