Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Tipp
Neu bei der Entwicklung von Software? Beginnen Sie zuerst mit den Lernprogrammen " Erste Schritte ". Sobald Sie Sammlungen wie List<T> verwenden, begegnen Sie generischen Typen.
Haben Sie Erfahrung in einer anderen Sprache? C#-Generika ähneln Generika in Java oder Vorlagen in C++, aber mit vollständigen Laufzeittypinformationen und ohne Typlöschung. Überspringen Sie die Auflistungsausdrücke und Kovarianz- und Kontravarianzabschnitte für C#-spezifische Muster.
Mit Generics können Sie Code schreiben, der mit jedem Typ funktioniert, während die vollständige Typsicherheit beibehalten wird. Anstatt separate Klassen oder Methoden für int, stringund alle anderen benötigten Typen zu schreiben, schreiben Sie eine Version mit einem oder mehreren Typparametern (z T. B. oder TKey und TValue) und geben Sie die tatsächlichen Typen an, wenn Sie sie verwenden. Der Compiler überprüft Typen zur Kompilierungszeit, sodass Sie keine Umwandlungen zur Laufzeit benötigen oder ein Risiko eingehen InvalidCastException.
Sie begegnen Generics ständig in Ihrem alltäglichen Umgang mit C#. Auflistungen, asynchrone Rückgabetypen, Delegaten und LINQ stützen sich auf generische Typen.
List<int> scores = [95, 87, 72, 91];
Dictionary<string, decimal> prices = new()
{
["Widget"] = 19.99m,
["Gadget"] = 29.99m
};
Task<string> greeting = Task.FromResult("Hello, generics!");
Func<int, bool> isPositive = n => n > 0;
Console.WriteLine($"First score: {scores[0]}");
Console.WriteLine($"Widget price: {prices["Widget"]:C}");
Console.WriteLine($"Greeting: {await greeting}");
Console.WriteLine($"Is 5 positive? {isPositive(5)}");
In jedem Fall teilt das Typargument in eckigen Klammern (<int>, <string>, <Product>) dem generischen Typ mit, welche Art von Daten er enthält oder auf denen er arbeitet. Der Compiler erzwingt die Typsicherheit. Sie können nicht versehentlich ein string zu einem List<int> hinzufügen.
Verwenden generischer Typen
Verwenden Sie häufiger generische Typen aus der .NET-Klassenbibliothek, anstatt eigene zu erstellen. In den folgenden Abschnitten werden die gängigsten generischen Typen angezeigt, die Sie verwenden werden.
Generische Sammlungen
Der System.Collections.Generic Namespace stellt typsichere Sammlungsklassen bereit. Verwenden Sie diese Auflistungen immer anstelle nichtgenerischer Auflistungen wie ArrayList:
// A strongly typed list of strings
List<string> names = ["Alice", "Bob", "Carol"];
names.Add("Dave");
// names.Add(42); // Compile-time error: can't add an int to List<string>
// A dictionary mapping string keys to int values
var inventory = new Dictionary<string, int>
{
["Apples"] = 50,
["Oranges"] = 30
};
inventory["Bananas"] = 25;
// A set that prevents duplicates
HashSet<int> uniqueIds = [1, 2, 3, 1, 2];
Console.WriteLine($"Unique count: {uniqueIds.Count}"); // 3
// A FIFO queue
Queue<string> tasks = new();
tasks.Enqueue("Build");
tasks.Enqueue("Test");
Console.WriteLine($"Next task: {tasks.Dequeue()}"); // Build
Generische Auflistungen verhindern Typfehler zur Laufzeit, da die Fehler stattdessen zur Kompilierungszeit angezeigt werden. Diese Sammlungen vermeiden auch Boxing für Werttypen, wodurch die Leistung verbessert wird.
Generische Methoden
Eine generische Methode deklariert ihren eigenen Typparameter. Der Compiler leitet häufig das Typargument aus den übergebenen Werten ab , sodass Sie es nicht explizit angeben müssen:
static void Print<T>(T value) =>
Console.WriteLine($"Value: {value}");
Print(42); // Compiler infers T as int
Print("hello"); // Compiler infers T as string
Print(3.14); // Compiler infers T as double
Im Aufruf Print(42) leitet der Compiler T als int aus dem Argument ab. Sie können explizit schreiben Print<int>(42) , aber die Typinferenz sorgt dafür, dass der Code sauberer bleibt.
Sammlungsausdrücke
Sammlungsausdrücke (C# 12) stellen eine präzise Syntax zum Erstellen von Sammlungen bereit. Verwenden Sie eckige Klammern anstelle von Konstruktoraufrufen oder Initialisierungssyntax:
// Create a list with a collection expression
List<string> fruits = ["Apple", "Banana", "Cherry"];
// Create an array
int[] numbers = [1, 2, 3, 4, 5];
// Works with any supported collection type
IReadOnlyList<double> temperatures = [72.0, 68.5, 75.3];
Console.WriteLine($"Fruits: {string.Join(", ", fruits)}");
Console.WriteLine($"Numbers: {string.Join(", ", numbers)}");
Console.WriteLine($"Temps: {string.Join(", ", temperatures)}");
Der Spread-Operator (..) gliedert die Elemente einer Sammlung in eine andere ein, was für die Kombination von Sequenzen nützlich ist:
List<int> first = [1, 2, 3];
List<int> second = [4, 5, 6];
// Spread both lists into a new combined list
List<int> combined = [.. first, .. second];
Console.WriteLine(string.Join(", ", combined));
// Output: 1, 2, 3, 4, 5, 6
// Add extra elements alongside spreads
List<int> withExtras = [0, .. first, 99, .. second];
Console.WriteLine(string.Join(", ", withExtras));
// Output: 0, 1, 2, 3, 99, 4, 5, 6
Sammlungsausdrücke funktionieren mit Arrays, List<T>, Span<T>, ImmutableArray<T> und jedem Typ, der das Collection Builder-Muster unterstützt. Die vollständige Syntaxreferenz finden Sie unter Sammlungsausdrücke.
Wörterbuchinitialisierung
Sie können Wörterbücher präzise mit Indexerinitialisierern initialisieren. Diese Syntax verwendet eckige Klammern zum Festlegen von Schlüsselwertpaaren:
Dictionary<string, int> scores = new()
{
["Alice"] = 95,
["Bob"] = 87,
["Carol"] = 92
};
foreach (var (name, score) in scores)
{
Console.WriteLine($"{name}: {score}");
}
Sie können Wörterbücher zusammenführen, indem Sie ein Wörterbuch kopieren und Überschreibungen anwenden.
Dictionary<string, int> defaults = new()
{
["Timeout"] = 30,
["Retries"] = 3
};
Dictionary<string, int> overrides = new()
{
["Timeout"] = 60
};
// Merge defaults and overrides into a new dictionary
Dictionary<string, int> config = new(defaults);
foreach (var (key, value) in overrides)
{
config[key] = value;
}
Console.WriteLine($"Timeout: {config["Timeout"]}"); // 60
Console.WriteLine($"Retries: {config["Retries"]}"); // 3
Typeinschränkungen
Einschränkungen beschränken, welche Typargumente ein generischer Typ oder eine generische Methode akzeptiert. Mit Einschränkungen können Sie Methoden aufrufen oder Eigenschaften des Typparameters zugreifen, die nicht allein auf object verfügbar wären:
static T Max<T>(T a, T b) where T : IComparable<T> =>
a.CompareTo(b) >= 0 ? a : b;
Console.WriteLine(Max(3, 7)); // 7
Console.WriteLine(Max("apple", "banana")); // banana
static T CreateDefault<T>() where T : new() => new T();
var list = CreateDefault<List<int>>(); // Creates an empty List<int>
Console.WriteLine($"Empty list count: {list.Count}"); // 0
Die häufigsten Einschränkungen sind:
| Constraint | Bedeutung |
|---|---|
where T : class |
T muss ein Bezugstyp sein |
where T : struct |
T muss ein werttyp sein, der nicht nullwertebar ist. |
where T : new() |
T muss über einen öffentlichen parameterlosen Konstruktor verfügen |
where T : BaseClass |
T muss von BaseClass |
where T : IInterface |
T muss IInterface implementiert werden |
Sie können Einschränkungen kombinieren: where T : class, IComparable<T>, new(). Weniger häufige Einschränkungen umfassen where T : System.Enum, where T : System.Delegateund where T : unmanaged für spezielle Szenarien. Die vollständige Liste finden Sie unter Einschränkungen für Typparameter.
Kovarianz und Kontravarianz
Kovarianz und Kontravarianz beschreiben, wie sich generische Typen mit Vererbung verhalten. Sie bestimmen, ob Sie ein abgeleitetes oder weniger abgeleitetes Typargument als ursprünglich angegeben verwenden können:
// Covariance: IEnumerable<Dog> can be used as IEnumerable<Animal>
// because IEnumerable<out T> is covariant
List<Dog> dogs = [new("Rex"), new("Buddy")];
IEnumerable<Animal> animals = dogs; // Allowed because Dog derives from Animal
foreach (var animal in animals)
{
Console.WriteLine(animal.Name);
}
// Contravariance: Action<Animal> can be used as Action<Dog>
// because Action<in T> is contravariant
Action<Animal> printAnimal = a => Console.WriteLine($"Animal: {a.Name}");
Action<Dog> printDog = printAnimal; // Allowed because any Animal handler can handle Dog
printDog(new Dog("Spot"));
- de-DE: Kovarianz (
out T): EinIEnumerable<Dog>kann dort verwendet werden, woIEnumerable<Animal>erwartet wird, weilDogvonAnimalabgeleitet ist. DasoutSchlüsselwort für den Typparameter aktiviert dies. Kovariante Typparameter können nur an Ausgabepositionen (Rückgabetypen) angezeigt werden. -
Contravariance (
in T): EinAction<Animal>kann verwendet werden, wenn einAction<Dog>erwartet wird, da alle Aktionen, die mitAnimalumgehen können, auch mitDogumgehen können. DasinSchlüsselwort aktiviert dies. Contravariant-Typparameter können nur in Eingabepositionen (Parameter) angezeigt werden.
Viele integrierte Schnittstellen und Delegaten sind bereits Varianten: IEnumerable<out T>, IReadOnlyList<out T>, Func<out TResult> und Action<in T>. Sie profitieren automatisch von der Varianz bei der Arbeit mit diesen Typen. Eine ausführliche Behandlung des Entwerfens von Variantenschnittstellen und Delegaten finden Sie unter "Kovarianz" und "Kontravarianz".
Erstellen eigener generischer Typen
Sie können eigene generische Klassen, Strukturen, Schnittstellen und Methoden definieren. Das folgende Beispiel zeigt eine einfache generische verknüpfte Liste zur Veranschaulichung. In der Praxis können Sie List<T> oder eine andere integrierte Sammlung verwenden.
public class GenericList<T>
{
private class Node(T data)
{
public T Data { get; set; } = data;
public Node? Next { get; set; }
}
private Node? head;
public void AddHead(T data)
{
var node = new Node(data) { Next = head };
head = node;
}
public IEnumerator<T> GetEnumerator()
{
var current = head;
while (current is not null)
{
yield return current.Data;
current = current.Next;
}
}
}
var list = new GenericList<int>();
for (var i = 0; i < 5; i++)
{
list.AddHead(i);
}
foreach (var item in list)
{
Console.Write($"{item} ");
}
Console.WriteLine();
// Output: 4 3 2 1 0
Generische Typen sind nicht auf Klassen beschränkt. Sie können generische interface, und structrecord Typen definieren. Weitere Informationen zum Entwerfen generischer Algorithmen und komplexer Einschränkungskombinationen finden Sie unter Generics in .NET.