Generics in C#: warum sie wichtig sind und wie man sie in der Praxis einsetzt
Generics (Generika) sind eine der Schlüsselfunktionen der Sprache C#, die es ermöglicht, flexiblen, wiederverwendbaren und typsicheren Code zu schreiben. In diesem Artikel werden wir untersuchen, warum Generics benötigt werden, wie sie unter der Haube funktionieren, und praktische Beispiele für ihre Verwendung in realen Projekten zeigen.
Was sind Generics und warum werden sie benötigt?
Stellen Sie sich vor, Sie müssen eine Methode implementieren, die zwei Werte vertauscht. Ohne Generics müssten Sie separate Versionen für jeden Datentyp schreiben:
// Ohne Generics – Code-Duplizierungvoid SwapInt(ref int a, ref int b) { int temp = a; a = b; b = temp; }void SwapString(ref string a, ref string b) { string temp = a; a = b; b = temp; }// ... und so für jeden TypMit Generics wird dieses Problem elegant gelöst:
// Mit Generics – eine Methode für alle Typenvoid Swap<T>(ref T a, ref T b){ T temp = a; a = b; b = temp;}
// Verwendungint x = 1, y = 2;Swap(ref x, ref y); // T = int
string s1 = "Hello", s2 = "World";Swap(ref s1, ref s2); // T = stringHauptvorteile von Generics:
- Typsicherheit – Typfehler werden bereits zur Kompilierzeit erkannt
- Leistung – kein Boxing/Unboxing bei Werttypen
- Code-Wiederverwendung – eine Klasse/Methode arbeitet mit beliebigen Typen
- Lesbarkeit – der Code wird verständlicher und prägnanter
Generische Klassen und Strukturen
Das häufigste Beispiel für die Verwendung von Generics sind Sammlungen. Anstatt für jeden Typ eine eigene Klasse zu schreiben, erstellen wir eine generische:
public class MyList<T>{ private T[] _items; private int _count;
public MyList(int capacity = 4) { _items = new T[capacity]; _count = 0; }
public void Add(T item) { if (_count == _items.Length) { Array.Resize(ref _items, _items.Length * 2); } _items[_count++] = item; }
public T Get(int index) { if (index < 0 || index >= _count) throw new IndexOutOfRangeException(); return _items[index]; }
public int Count => _count;}
// Verwendungvar numbers = new MyList<int>();numbers.Add(10);numbers.Add(20);
var names = new MyList<string>();names.Add("Alice");Beachten Sie: Bei der Erstellung einer Instanz geben wir den konkreten Typ in spitzen Klammern <T> an. Der Compiler setzt diesen Typ an allen Stellen ein, an denen T verwendet wird.
Generische Methoden und Einschränkungen (Constraints)
Manchmal ist es notwendig, Einschränkungen für den Typ T festzulegen. Zum Beispiel, um sicherzustellen, dass der Typ ein bestimmtes Interface implementiert oder einen parameterlosen Konstruktor hat:
// Einschränkung: T muss IComparable<T> implementierenpublic T GetMax<T>(T a, T b) where T : IComparable<T>{ return a.CompareTo(b) > 0 ? a : b;}
// Einschränkung: T muss einen parameterlosen Konstruktor habenpublic class Factory<T> where T : new(){ public T Create() { return new T(); }}
// Einschränkung: T muss ein Werttyp sein (struct)public struct Nullable<T> where T : struct{ private T _value; private bool _hasValue;
public Nullable(T value) { _value = value; _hasValue = true; }
public T Value => _hasValue ? _value : throw new InvalidOperationException(); public bool HasValue => _hasValue;}Haupttypen von Einschränkungen:
where T : class– Referenztypwhere T : struct– WerttypBlogs
Buchempfehlungen
Lesen Sie auch
Beste IDEs und Tools für die PHP-Entwicklung im Jahr 2024
Konstante Arrays in Go: Wie man sie deklariert und nicht verrückt wird
CI/CD für Kotlin-Projekte: GitHub Actions von A bis Z
Warum gibt es in Java keine Standardparameter? Eleganter Workaround
Go: Prüfen, ob ein Teilstring in einem String enthalten ist – vollständige Anleitung