CodeGym /Kurse /C# SELF /Call Stack und eigene Exceptions erstellen

Call Stack und eigene Exceptions erstellen

C# SELF
Level 13 , Lektion 3
Verfügbar

1. Einführung in den Call Stack

In der letzten Vorlesung haben wir den Call Stack kurz erwähnt, jetzt schauen wir ihn uns genauer an.

Stell dir vor: Ein User klickt auf einen Button in deiner App. Dieser Button ruft die Methode OnClick() ("Button wurde geklickt") auf. Die wiederum ruft LoadData() (Daten laden) auf, und die dann ReadFromFile() (aus Datei lesen).

Plötzlich passiert in ReadFromFile() ein Fehler – Datei nicht gefunden. Wer ist schuld?

Um das rauszufinden, geht das Programm „zurück auf den Spuren“: von ReadFromFile() → zu LoadData() → zu OnClick(). Dieser Weg ist der Call Stack – wie ein Stapel Teller, wobei der letzte oben als erstes runterfällt.

Das Programm geht diesen Stapel runter, bis es ein passendes catch findet, führt aber alle finally aus, die auf dem Weg liegen – damit alles geschlossen, freigegeben und aufgeräumt wird.

Im Programmieren ist der Call Stack eine Liste, die sich merkt, wer wen aufgerufen hat, damit man im Fehlerfall diesen „Request-Pfad“ zurückgehen kann, um die Ursache zu finden.

Wie das funktioniert

Wenn ein Programm läuft, fügt jeder Methoden- (oder Funktions-)Aufruf eine neue „Zeile“ zum Call Stack (zur Liste) hinzu. Wenn währenddessen eine Exception passiert, speichert die .NET-Klasse Exception Infos über diesen Stack: Wo und in welcher Reihenfolge wurden Methoden aufgerufen, die zum Fehler geführt haben.

2. Wozu braucht man überhaupt einen Call Stack

Der Call Stack ist dein bester Freund beim Debuggen von komplizierten Fehlern.

  • Er zeigt nicht nur was passiert ist, sondern auch wo genau und wer schuld ist.
  • Manchmal staunst du beim Blick auf den Stack, wie das Programm überhaupt in diesen Zustand gekommen ist (vor allem, wenn jemand aus Versehen den falschen Argumentwert übergeben hat).

Typische Story: Stell dir vor, du hast ein riesiges Projekt, in dem Methoden sich gegenseitig auf zig Ebenen aufrufen. Plötzlich taucht ein NullReferenceException auf, aber du hast keinen Plan, wie das Programm da hingekommen ist. Stack öffnen – ganze Aufrufkette sehen, und schon ist klarer, wo du anfangen musst zu suchen.

Beispiel:

class MyClass 
{
    public void MethodA()     { MethodB(); }
    
    public void MethodB()     { MethodC(); }
    
    public void MethodC()     {
        throw new Exception("Fehler!");
    }    

    public void Main() 
    {
        try 
        { 
            MethodA(); 
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.StackTrace);
        }        
    }
}

3. Wie man eigene Exceptions erstellt

Manchmal reichen die Standard-Exceptions nicht aus

In .NET gibt es jede Menge Standard-Exceptions (ArgumentNullException, InvalidOperationException usw.), aber manchmal reicht das nicht:

  • Deine App hat eigene „Spielregeln“: Zum Beispiel darf ein User nicht mehr als 10 Produkte auf einmal kaufen, oder in deiner Business-Logik dürfen keine negativen Summen vorkommen.
  • Du willst Logik-Fehler von „Systemfehlern“ trennen.

Hier kommt der Wunsch auf, „sein eigenes Ding“ zu machen – wenn’s schon geht!


using System;

// Eigene Exception: User nicht gefunden
public class UserNotFoundException : Exception
{
    // Basis-Konstruktor
    public UserNotFoundException() : base("Benutzer nicht gefunden.") { }

    // Konstruktor mit Nachricht
    public UserNotFoundException(string message) : base(message) { }

    // Konstruktor mit Nachricht und innerer Exception
    public UserNotFoundException(string message, Exception inner) : base(message, inner) { }
}
Beispiel für eine eigene Exception UserNotFoundException

Kurz zu den Konstruktoren

  • Ohne Parameter – setzt die Standard-Nachricht
  • Mit eigener Nachricht – manchmal will man Details hinzufügen
  • Mit innerer Exception (inner) – damit bei Fehlern „in einer anderen Exception“ keine wichtigen Infos verloren gehen.

Wie man die eigene Exception benutzt

Angenommen, wir modellieren eine Methode zur User-Suche in unserer Aufgabenverwaltungs-App:

using System;

public class UserService
{
    public string FindUserNameById(int userId)
    {
        // „Suchen“ nach User, wenn nicht gefunden – Exception werfen
        if (userId != 42)
            throw new UserNotFoundException($"Benutzer mit id {userId} nicht gefunden.");

        return "Maxim";
    }
}

Im Hauptprogramm:

// In Main
var service = new UserService();
try
{
    string name = service.FindUserNameById(17);
    Console.WriteLine("Benutzername: " + name);
}
catch (UserNotFoundException ex)
{
    Console.WriteLine("Problem bei der Benutzersuche: " + ex.Message);
    // Call Stack ist hier auch über ex.StackTrace verfügbar
}

Wenn wir eine id ungleich 42 übergeben, bekommen wir:

Problem bei der Benutzersuche: Benutzer mit id 17 nicht gefunden.

4. Gründe, eigene Exceptions zu erstellen

Logging und Fehlertrennung

Angenommen, deine App hat viele verschiedene Fehler, die unterschiedlich behandelt werden müssen. Zum Beispiel: Datenbankfehler loggst du als „fatal“, User-Fehler zeigst du dem User als rote Meldung, Netzwerkfehler – da versuchst du die Operation zu wiederholen. Gruppierung nach Exception-Typ ist dafür super.

OOP und Vererbung

Du kannst eine Fehler-Hierarchie für deinen Fachbereich bauen:

public class MyAppException : Exception { ... }
public class OrderException : MyAppException { ... }
public class ProductException : MyAppException { ... }
public class TooManyItemsInOrderException : OrderException { ... }

Jetzt kannst du mit MyAppException alle Fehler deines Fachbereichs abfangen, und wenn du speziell auf „zu große Bestellung“ reagieren willst – fängst du den Spezialfall.

5. Was du beim Erstellen eigener Exceptions beachten solltest

  • Exceptions nicht nur um der Exceptions willen werfen
    Erstelle eigene Exceptions nur, wenn:
    • Es hilft, den Code verständlicher zu machen
    • Es eine Chance gibt, dass sie vom aufrufenden Code gefangen werden
    • Du dem aufrufenden Code mehr Infos geben willst (über Felder/Properties)
  • Gute Praxis: Serialisierung
    In .NET unterstützen Standard-Exceptions die Serialisierung (z.B. für Netzwerkübertragung). In einfachen Apps braucht man das selten, aber für „fortgeschrittene“ Fälle – füge das Attribut [Serializable] hinzu und implementiere einen Konstruktor für die Serialisierung (siehe offizielle Doku). Für Lernbeispiele kann man das weglassen, aber im Job – frag lieber den Teamlead :)
    Serialisierung ist der Prozess, ein Objekt in ein Format zu bringen, das sich gut speichern oder übertragen lässt (z.B. in eine Datei oder übers Netzwerk). Wir schauen uns Serialisierung später noch genauer an.

6. Besonderheiten des Call Stack: Wo kann es verwirrend werden

Der Call Stack zeigt nur den Weg bis zu dem Punkt, wo die Exception passiert ist. Wenn du eine Exception fängst und eine neue wirfst, ohne die „innere“ Exception anzugeben (siehe Konstruktor Exception(string, Exception inner)), verlierst du die ursprüngliche Fehlerquelle. Das nennt man „Stack verstecken“.

Schlecht:

try
{
    // Hier passiert ein Fehler
}
catch (Exception ex)
{
    throw new Exception("Unbekannter Fehler ist aufgetreten."); // Alter Stack geht verloren!
}

Gut:

try
{
    // Hier passiert ein Fehler
}
catch (Exception ex)
{
    throw new Exception("Unbekannter Fehler ist aufgetreten.", ex); // Original-Stack bleibt erhalten!
}

Dann bleibt im StackTrace sowohl der Weg zum ersten Fehler als auch deine neue Nachricht erhalten.

7. Praktische Tipps und häufige Fehler

  • Wirf keine Exceptions, um normale Logik zu steuern (z.B. für „Schleifenabbruch“ – dafür gibt’s elegantere Wege!).
  • Fang den richtigen Exception-Typ – immer Exception zu fangen ist nicht immer eine gute Idee (du könntest einen Fehler „verschlucken“, den du nicht solltest).
  • Logge immer den Call Stack bei komplizierten Fehlern. Das spart dir viele Nerven beim Bug-Suchen.
  • Nutze innere Exceptions – so gehen Infos zur Fehlerursache nicht verloren.
  • Beschreibe deine eigenen Exceptions verständlich – damit jemand, der den Code in einem Jahr liest, versteht, warum der Fehler aufgetreten ist.
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION