Null-Referenzen
null bedeutet "kein Objekt". Der Zugriff darauf löst eine NullReferenceException aus, den berühmtesten Fehler der Programmierung. Seit C# 8 helfen nullable reference types: Der Compiler unterscheidet string (nie null) und string? (kann null sein) und warnt bei Risiken.
string? eingabe = null;
string name = eingabe ?? "Gast"; // Ersatzwert
int laenge = eingabe?.Length ?? 0; // sicherer Zugriff
Console.WriteLine($"{name} {laenge}");
if (eingabe is not null) Console.WriteLine(eingabe.ToUpper()); // Compiler weiß: hier nicht null
else Console.WriteLine("keine Eingabe");
string sicher = eingabe!; // ! = "ich weiß es besser" (Warnung unterdrücken, Vorsicht!)
Console.WriteLine(sicher is null);
ArgumentNullException.ThrowIfNull(name); // Parameter prüfen
try { Pruefe(null!); } catch (ArgumentNullException e) { Console.WriteLine(e.ParamName); }
void Pruefe(string text) { ArgumentNullException.ThrowIfNull(text); }Gast 0 keine Eingabe True text
Aktiviert wird das Feature mit <Nullable>enable</Nullable> in der Projektdatei (in neuen Projekten Standard). Nutze den !-Operator nur, wenn du es sicher weißt.
Ausnahmen
try
{
Console.WriteLine("vorher");
int x = int.Parse("abc");
Console.WriteLine("wird nie erreicht " + x);
}
catch (FormatException e)
{
Console.WriteLine("Ungültige Zahl: " + e.Message);
}
finally
{
Console.WriteLine("läuft immer");
}
Console.WriteLine("Programm läuft weiter");vorher Ungültige Zahl: The input string 'abc' was not in a correct format. läuft immer Programm läuft weiter
Mehrere catch-Blöcke und Filter
foreach (int variante in new[] { 0, 1, 2, 3, 4 })
{
try { Teste(variante); }
catch (DivideByZeroException) { Console.WriteLine("Division durch 0"); }
catch (IndexOutOfRangeException e) { Console.WriteLine("Index: " + e.Message); }
catch (ArgumentException e) when (e.ParamName == "wert") { Console.WriteLine("Argument wert"); }
catch (Exception e) { Console.WriteLine("Sonstiges: " + e.GetType().Name); }
}
void Teste(int v)
{
int zero = 0;
switch (v)
{
case 0: Console.WriteLine(1 / zero); break;
case 1: { int[] feld = new int[2]; Console.WriteLine(feld[v + 5]); break; }
case 2: throw new ArgumentException("kaputt", "wert");
case 3: throw new InvalidOperationException("Zustand");
default: Console.WriteLine("ok"); break;
}
}Division durch 0 Index: Index was outside the bounds of the array. Argument wert Sonstiges: InvalidOperationException ok
Die Reihenfolge ist wichtig: spezifisch vor allgemein. Mit when filterst du nach Bedingung.
Eigene Ausnahmen
try
{
Console.WriteLine(Abheben(100, 30));
Console.WriteLine(Abheben(100, 130));
}
catch (GuthabenException e)
{
Console.WriteLine($"{e.Message}, es fehlen {e.Fehlbetrag}");
}
decimal Abheben(decimal stand, decimal betrag)
=> betrag > stand ? throw new GuthabenException(betrag - stand) : stand - betrag;
class GuthabenException(decimal fehlbetrag) : Exception("Nicht genug Guthaben")
{
public decimal Fehlbetrag { get; } = fehlbetrag;
}70 Nicht genug Guthaben, es fehlen 30
Ausnahmen weiterreichen
try { Aeussere(); }
catch (Exception e) { Console.WriteLine($"{e.Message} <- {e.InnerException?.Message}"); }
void Aeussere()
{
try { int.Parse("x"); }
catch (FormatException e)
{
throw new InvalidOperationException("Konfiguration ungültig", e); // Ursache mitgeben
}
}
void Weiter()
{
try { Aeussere(); }
catch (Exception) { Console.WriteLine("log"); throw; } // throw; bewahrt den Stacktrace
}Konfiguration ungültig <- The input string 'x' was not in a correct format.
Schreibe throw; (nicht throw e;), wenn du eine Ausnahme nach dem Loggen weiterwirfst: Sonst geht der ursprüngliche Stacktrace verloren.
Der Stacktrace
Teilen(1, 0);
int Teilen(int a, int b) => a / b;Unhandled exception. System.DivideByZeroException: Attempted to divide by zero.
Guter Umgang
- Nur fangen, was du behandeln kannst; kein leeres
catch - Erwartbare Fälle mit
TryParse,TryGetValue,Result-Typen statt Ausnahmen lösen (Ausnahmen sind langsam) - Parameter am Methodenanfang prüfen (
ArgumentNullException.ThrowIfNull,ArgumentOutOfRangeException.ThrowIfNegative) - Ressourcen mit
usingfreigeben - Gib Ausnahmen aussagekräftige Meldungen und die Ursache (
innerException)
Merke
- Nullable Reference Types:
string?kann null sein;??,?.,is not nullbehandeln es try/catch/finally; spezifischecatch-Blöcke vor allgemeinen;whenfiltert- Eigene Ausnahmen erben von
Exception throw;bewahrt den Stacktrace; Ursache überinnerExceptionTryXyz-Methoden vermeiden Ausnahmen im Normalfall
Aufgabe
Schreibe Wurzel(double x), die bei negativer Zahl eine ArgumentOutOfRangeException wirft, und TryWurzel(double x, out double ergebnis).