20% off with code ERSTIS26
5 min read 02 Sept 2026

C# Generics einfach erklärt: Typparameter & generische Klassen

Generics in C# einfach erklärt: Typparameter, generische Klassen und Methoden – ideal für Azubis, Studenten und Berufseinsteiger.

Marcel Schmidtpeter By Marcel Schmidtpeter 02 Sept 2026

Dein Ausbilder möchte eine kleine Artikelverwaltung, und du schreibst zum ersten Mal `List<Artikel>` in den Editor. Was genau bedeutet dieses `<Artikel>` in den spitzen Klammern? Die Antwort heißt C# Generics - und ohne dieses Konzept wären Listen, Dictionaries und fast alle Collections in C# kaum benutzbar. Ob du gerade deine IHK-Projektprüfung vorbereitest, im Studium Datenstrukturen hörst oder im ersten Job auf fremden Code triffst: Irgendwo begegnen dir die spitzen Klammern garantiert.

Was sind C# Generics?

Generics („Generika") sind Klassen, Methoden oder Schnittstellen, die mit einem Platzhalter für einen Datentyp arbeiten. Diesen Platzhalter nennt du Typparameter. Erst bei der Verwendung legst du fest, welcher konkrete Typ eingesetzt wird.

Stell dir ein Formular mit einem leeren Feld vor: Die Struktur steht fest, aber was du ins Feld schreibst, entscheidest du erst beim Ausfüllen. Genau so funktionieren Generics - nur dass der Compiler beim Kompilieren das Feld mit einem echten Typ ausfüllt und dabei prüft, ob alles zusammenpasst.

Merke: Generics = Code einmal schreiben, für beliebig viele Typen verwenden - mit voller Typprüfung zur Kompilierzeit.

Warum nicht einfach object verwenden?

Kurzantwort: Weil du damit die Typsicherheit zur Kompilierzeit verlierst. Der naheliegende Trick ohne Generics: Du speicherst alles als `object`, denn in C# erbt jeder Typ von `object`. Das funktioniert - wird aber schnell zum Problem.

csharp
public class Box
{
  private object inhalt;

  public void Hinzufuegen(object wert)
  {
    inhalt = wert;
  }

  public object Holen()
  {
    return inhalt;
  }
}

Bei der Verwendung brauchst du einen Cast (eine explizite Typumwandlung) - und der kann zur Laufzeit schiefgehen:

csharp
Box box = new Box();
box.Hinzufuegen(42);
int zahl = (int)box.Holen(); // Cast noetig

box.Hinzufuegen("Hallo");
int kaputt = (int)box.Holen(); // InvalidCastException zur Laufzeit!

Der Compiler merkt hier nichts - der Fehler knallt erst, wenn das Programm läuft.

Merke: Laufzeitfehler sind die teuersten Fehler - in der Prüfung wie im Betrieb entdeckst du sie erst spät.

Kriterium

object-Ansatz

Generics

Typsicherheit

erst zur Laufzeit

schon beim Kompilieren

Casting

immer nötig

nicht nötig

Performance

Boxing bei Werttypen

kein Boxing

Wartbarkeit

fehleranfällig

typsicher

Boxing bedeutet: Ein Werttyp wie `int` wird beim Speichern in `object` in ein Objekt auf dem Heap verpackt - das kostet Zeit und Speicher. Generics vermeiden das. Details dazu findest du in der offiziellen C#-Dokumentation zu Generics.

Was ist ein Typparameter genau?

Ein Typparameter ist ein Bezeichner (meist `T`), der innerhalb einer generischen Klasse oder Methode als Platzhalter für einen noch unbekannten Typ steht. Bei der Verwendung ersetzt du ihn durch einen konkreten Typ - das nennt man Typargument.

csharp
Box<int> zahlenBox;    // T wird zu int
Box<string> textBox;   // T wird zu string

Merke: Der Typparameter `T` ist der Platzhalter in der Definition - das Typargument (`int`, `string`, ...) ist der konkrete Typ bei der Verwendung.

Der Name `T` ist reine Konvention, genau wie sprechende Variablennamen. Bei einem Typparameter für Artikel wäre `TArtikel` völlig legitim. Bei zwei Typparametern - etwa bei einem `Dictionary<TKey, TValue>` - siehst du das Prinzip doppelt: Schlüssel und Wert bekommen jeweils einen eigenen Platzhalter.

Wie erstellst du eine generische Klasse?

Eine generische Klasse schreibst du, indem du den Typparameter in spitzen Klammern hinter den Klassennamen setzt und ihn im Code wie einen normalen Typ verwendest.

csharp
public class Box<T>
{
  private T inhalt;

  public void Hinzufuegen(T wert)
  {
    inhalt = wert;
  }

  public T Holen()
  {
    return inhalt;
  }
}

Die Verwendung:

csharp
Box<int> zahlenBox = new Box<int>();
zahlenBox.Hinzufuegen(42);
int zahl = zahlenBox.Holen(); // kein Cast, kein Boxing

Box<string> textBox = new Box<string>();
textBox.Hinzufuegen("Hallo");
string text = textBox.Holen();

Versuchst du, in `zahlenBox` einen String zu lagern, meldet sich der Compiler sofort mit einem Fehler. Genau diese Sicherheit willst du - auch wenn's beim Lernen erstmal nervt. Du kennst das Prinzip übrigens schon: `List<T>` aus dem C# Collections Überblick ist selbst eine generische Klasse, genauso wie die Klassen aus dem Vergleich List vs. LinkedList.

Wie funktionieren generische Methoden?

Auch einzelne Methoden können generisch sein - der Typparameter steht dann in spitzen Klammern hinter dem Methodennamen. Klassiker: zwei Werte tauschen.

csharp
public class Werkzeuge
{
  public static void Tausche<T>(ref T a, ref T b)
  {
    T temp = a;
    a = b;
    b = temp;
  }
}

// Verwendung:
int x = 1, y = 2;
Werkzeuge.Tausche(ref x, ref y); // x = 2, y = 1

Das `ref` sorgt dafür, dass die Methode die Originalvariablen verändert, nicht Kopien. Eine Methode, null Duplikate, unendlich viele Typen - der ganze Trick steckt in den spitzen Klammern.

Was sind Constraints - Typparameter einschränken?

Constraints (Typeinschränkungen) legen mit der `where`-Klausel fest, welche Typen für den Typparameter erlaubt sind. Ohne Constraints darfst du mit `T` fast nichts machen - nicht einmal `a + b` addieren oder `a.CompareTo(b)` aufrufen.

Merke: Ohne Constraints behandelt der Compiler `T` wie `object` - nur `ToString()`, `Equals()` und Konsorten stehen dir zur Verfügung.

csharp
public static T GroessereZahl<T>(T a, T b) where T : IComparable<T>
{
  if (a.CompareTo(b) > 0)
  {
    return a;
  }
  return b;
}

Die wichtigsten Constraints im Überblick:

Constraint

Bedeutung

`where T : class`

T muss ein Referenztyp sein

`where T : struct`

T muss ein Werttyp sein

`where T : new()`

T braucht einen parameterlosen Konstruktor

`where T : IComparable<T>`

T muss die Schnittstelle implementieren

`where T : Fahrzeug`

T muss von dieser Klasse erben

Warum ist das nützlich? Weil du mit `where T : IComparable<T>` sicher sein kannst, dass jedes `T` verglichen werden kann. Schnittstellen wie `IEnumerable<T>` aus dem Artikel zu IEnumerable und foreach hängen eng mit Generics zusammen.

Praxisbeispiel: Ein generisches Lager

Hier ein vollständiges, lauffähiges Beispiel - ein Lager, das beliebige Artikeltypen verwaltet:

csharp
using System;
using System.Collections.Generic;

public class Artikel
{
  public string Name { get; set; }
  public decimal Preis { get; set; }
}

public class Lager<T> where T : class
{
  private List<T> bestand = new List<T>();

  public void Einlagern(T artikel)
  {
    bestand.Add(artikel);
  }

  public T Entnehmen()
  {
    T letzter = bestand[bestand.Count - 1];
    bestand.RemoveAt(bestand.Count - 1);
    return letzter;
  }
}

class Program
{
  static void Main()
  {
    Lager<Artikel> lager = new Lager<Artikel>();
    lager.Einlagern(new Artikel { Name = "Monitor", Preis = 199.99m });
    lager.Einlagern(new Artikel { Name = "Tastatur", Preis = 49.90m });

    Artikel artikel = lager.Entnehmen();
    Console.WriteLine("Entnommen: " + artikel.Name);

    // Gleicher Code, anderer Typ - ohne eine Zeile umzuschreiben:
    Lager<string> namen = new Lager<string>();
    namen.Einlagern("Anna");
    Console.WriteLine(namen.Entnehmen());
  }
}

Ein Code, zwei völlig unterschiedliche Anwendungsfälle. Genau das ist der Praxisnutzen von Generics - auch wenn reale Systeme oft noch Interfaces wie im Artikel zu anonymen Typen und Lambdas kombinieren.

Häufige Fehler bei C# Generics

Fehler 1: Mit T rechnen wollen. Ohne passenden Constraint kennt der Compiler für `T` keine Operatoren:

csharp
// FALSCH: kompiliert nicht
public static T Addiere<T>(T a, T b)
{
  return a + b; // Fehler: Operator '+' ist fuer T nicht definiert
}

Für Zahlen musst du auf `int`, `double` & Co. direkt zugreifen oder Interfaces wie `INumber<T>` (ab .NET 7) nutzen.

Fehler 2: Generics mit Vererbung verwechseln. `Box<int>` und `Box<string>` sind zwei komplett verschiedene Typen. Du kannst eine `Box<string>` nicht einer Variablen vom Typ `Box<object>` zuweisen, nur weil `string` von `object` erbt.

Merke: Das nennt sich Varianz - ein fortgeschrittenes Thema, das dich als Einsteiger erstmal nicht belasten muss.

Die Grundlagen von Vererbung klärt der Artikel zu OOP-Prinzipien in C# auf.

Fehler 3: Methoden aufrufen, die T nicht hat. Ohne `where T : IComparable<T>` gibt es kein `CompareTo` auf `T`. Der Compiler kennt nur `object`-Methoden wie `ToString()` und `Equals()` - mehr dazu im Artikel zu ToString, Equals und GetHashCode. Der Fehler: Constraint vergessen und sich wundern, warum die Methode fehlt.

Übungstipp: Baue deinen eigenen Stack

Nimm Stift und Papier (ehrlich gesagt - handschriftliches Üben festigt die Syntax enorm) und schreibe eine generische Klasse `Stapel<T>` mit den Methoden `Push` (ablegen), `Pop` (entnehmen) und `Peek` (nur anschauen). Nutze intern ein `T[]`-Array oder eine `List<T>`. Wenn du das ohne Vorlage hinbekommst, hast du Generics verstanden - und nebenbei eine klassische Prüfungsfrage aus dem Thema Datenstrukturen gelöst.

FAQ: Häufige Fragen zu C# Generics

Was bedeutet das T in `List<T>`?

`T` ist ein Typparameter - ein Platzhalter für den Datentyp der Listenelemente. Bei `List<int>` enthält die Liste nur Integers, bei `List<string>` nur Strings. Der Buchstabe `T` ist Konvention (engl. „Type"), du könntest ihn umbenennen.

Kann eine generische Klasse mehrere Typparameter haben?

Ja, beliebig viele. `Dictionary<TKey, TValue>` hat zwei, für Schlüssel und Wert. In der Praxis siehst du selten mehr als zwei oder drei.

Sind Generics schneller als der object-Ansatz?

Ja, vor allem bei Werttypen wie `int`, weil Generics Boxing vermeiden - das Umverpacken in ein Heap-Objekt entfällt. Bei Referenztypen ist der Unterschied gering, die Typsicherheit gewinnst du aber immer.

Was ist der Unterschied zwischen generischen Klassen und generischen Methoden?

Generische Klassen geben den Typparameter für das ganze Objekt vor, generische Methoden entscheiden ihn pro Aufruf. Beides kannst du kombinieren.

Kann ich Typparameter zur Laufzeit ändern?

Nein. Der Typ steht mit der Verwendung fest. Wenn du zur Laufzeit unterschiedliche Typen brauchst, erstellst du einfach zwei Instanzen, etwa `Lager<int>` und `Lager<string>`.

Fazit

C# Generics sind kein Zierwerk der Sprache, sondern das Fundament hinter `List<T>`, `Dictionary<TKey, TValue>` und unzähligen Bibliotheken. Das Wichtigste in drei Sätzen: Ein Typparameter ist ein Platzhalter für einen Typ, der erst bei der Verwendung festgelegt wird. Der Compiler prüft dann alles zur Kompilierzeit - keine Casts, kein Boxing, keine bösen Überraschungen zur Laufzeit. Mit `where`-Constraints schränkst du ein, was `T` können muss.

Wenn du bei C#, Datenstrukturen oder Prüfungsvorbereitung Unterstützung brauchst: Bei study-it.education findest du individuelle IT-Nachhilfe - für Azubis, Studenten und Quereinsteiger. Schau vorbei und mach aus den spitzen Klammern keine Stolpersteine mehr.


Matching tutoring

Want 1:1 help with this topic?

First chat · €0 · 30 min

Need someone to walk through this with you?

Instead of re-reading: 30 min with a senior dev.

Book a free first chat →
About the author
Marcel Schmidtpeter
Marcel Schmidtpeter

Generics in C# einfach erklärt: Typparameter, generische Klassen und Methoden – ideal für Azubis, Studenten und Berufseinsteiger.

marcel.schmidtpeter@study-it.education →
Keep reading

More reading