⚡ Lehrpfad 3: LINQ, Generics & Async/Await

C# 09: Generische Klassen, Methoden & Constraints

Erstelle typsichere, wiederverwendbare Datenstrukturen und Repositories ohne Boxing/Unboxing mit Repository<T> und mächtigen Generic Constraints (where T : class, new()).

📂 Dateien dieser Lerneinheit:
📄 Aufgabe.cs (Startercode) 🧪 TestAufgabe.cs (xUnit Tests) 💡 Musterloesung.cs (Lösung) 📖 README.md

🧬 1. Warum Generics? Typsicherheit & Zero-Cost Abstraction

Vor der Einführung von Generics mussten Collections mit dem Basistyp object arbeiten. Dies führte zu teurem Boxing & Unboxing von Wertetypen (Heap-Allokation) und gefährlichen Laufzeitfehlern (InvalidCastException).

Mit Generics (<T>) erzeugt der C#-Compiler zur Laufzeit für jeden Typ spezialisierten, hochoptimierten Maschinencode:

[Ohne Generics] ── int (42) ──► [BOXING: Heap-Objekt Allokation] ──► ArrayList ──► [UNBOXING: Cast] (Fehleranfällig!)
[Mit Generics] ── int (42) ──► List<int> [Direkter Stack/Array Speicher] ──► Keine Allokation, 100% typsicher!
// Generische Result-Wrapper-Klasse
public sealed class Result<T>
{
    public bool IsSuccess { get; }
    public T? Value { get; }
    public string? Error { get; }

    private Result(T value) { IsSuccess = true; Value = value; Error = null; }
    private Result(string error) { IsSuccess = false; Value = default; Error = error; }

    public static Result<T> Success(T value) => new(value);
    public static Result<T> Failure(string error) => new(error);
}

🔒 2. Generic Constraints (where T : ...)

Standardmäßig weiß der Compiler bei T nur, dass es ein object ist. Mit Constraints schränkst du die erlaubten Typen ein und erhältst Zugriff auf Konstruktoren, Interfaces oder Referenz-/Wertetyp-Eigenschaften:

// Generic Entity Interface mit flexibler ID
public interface IEntity<TId>
{
    TId Id { get; set; }
}

// Generic Repository mit mehreren Constraints:
// 1. TEntity muss eine Klasse sein (class)
// 2. TEntity muss IEntity<TId> implementieren
// 3. TEntity muss einen parameterlosen Konstruktor besitzen (new())
public class InMemoryRepository<TEntity, TId> 
    where TEntity : class, IEntity<TId>, new()
    where TId : notnull
{
    private readonly Dictionary<TId, TEntity> _storage = new();

    public void Add(TEntity entity) => _storage[entity.Id] = entity;
    public TEntity? GetById(TId id) => _storage.GetValueOrDefault(id);
    public IReadOnlyList<TEntity> GetAll() => _storage.Values.ToList().AsReadOnly();
    public bool Remove(TId id) => _storage.Remove(id);
    public int Count => _storage.Count;
}
🏢 Didaktische Analogie: Der genormte Industrie-Lagerplatz

Ein generisches Regal Regal<T> kann jeden Gegenstand lagern. Wenn wir jedoch where T : IPaket, new() verlangen, stellen wir sicher: Jeder gelagerte Gegenstand besitzt zwingend einen Barcode (IPaket) und die Lager-Robotik kann bei Bedarf eine leere Standardbox fabrikneu herstellen (new()).

📑 3. Cheat-Sheet: C# Generic Constraints

Constraint Bedeutung & Syntax Erlaubte Operationen / Nutzen
where T : class Typ muss ein Referenztyp sein Erlaubt null-Prüfungen, verhindert Wertetypen wie int
where T : struct Typ muss ein Wertetyp sein (nicht nullable) Speichereffizienz auf dem Stack, keine Heap-Allokation
where T : new() Muss öffentlichen parameterlosen Konstruktor haben Erlaubt Instanziierung im Code via new T()
where T : ISomeInterface Muss das angegebene Interface implementieren Direkter Aufruf aller Interface-Methoden und Properties auf T
where T : notnull Verbietet Nullable-Typen (z.B. für Dictionary-Keys) Garantierte Non-Nullability zur Compile-Zeit

🧠 Diagnostischer Selbst-Check

Überprüfe dein Verständnis von Sprach-Features, Speicherverwaltung und Enterprise-Architektur, bevor du den Code in der Web-IDE schreibst.

🎯 Aufgaben & Checkliste in Aufgabe.cs

💡 Enterprise C# Pro-Tipp: Das Repository-Muster

In modernen Clean-Architecture-Projekten abstrahiert ein generisches IRepository<TEntity, TId> den Datenbankzugriff (z.B. Entity Framework Core oder Cosmos DB). So lässt sich die gesamte Persistenzschicht in Unit-Tests kinderleicht durch ein In-Memory-Repository austauschen!

💻 In Web-IDE öffnen → Weiter zu C# 10: LINQ →