Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Tips/Råd
Är du nybörjare på att utveckla programvara? Börja med självstudierna Komma igång först. Du kommer att stöta på generiska läkemedel så snart du använder samlingar som List<T>.
Har du erfarenhet av ett annat språk? C#-generiska objekt liknar generiska objekt i Java eller mallar i C++, men med fullständig körningstypinformation och ingen typavsplåning. Skumma samlingsuttrycken och samvarians- och kontravariansavsnitten för C#-specifika mönster.
Med generiska objekt kan du skriva kod som fungerar med valfri typ samtidigt som fullständig typ av säkerhet bibehålls. I stället för att skriva separata klasser eller metoder för int, string och alla andra typer du behöver, skriver du en version med en eller flera typparametrar (till exempel T, eller TKey och TValue) och anger de faktiska typerna när du använder den. Kompilatorn kontrollerar typkontroll vid kompileringstillfället, så du behöver inte runtime-typomvandlingar eller risk InvalidCastException.
Du stöter ständigt på generics i C#. Samlingar, asynkrona returtyper, delegater och LINQ förlitar sig alla på generiska typer.
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)}");
I varje fall anger typargumentet i vinkelparenteser (<int>, <string>, <Product>) den generiska typen vilken typ av data det innehåller eller fungerar på. Kompilatorn tillämpar typsäkerhet. Du kan inte av misstag lägga till en string i en List<int>.
Konsumera generiska typer
Ofta använder du generiska typer från .NET-klassbiblioteket i stället för att skapa egna. I följande avsnitt visas de vanligaste generiska typerna som du använder.
Allmänna samlingar
Namnområdet System.Collections.Generic innehåller typsäkra samlingsklasser. Använd alltid dessa samlingar i stället för icke-generiska samlingar som 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
Generiska samlingar förhindrar typfel under körningstid eftersom felen uppstår vid kompilering i stället. Dessa samlingar undviker också boxning för värdetyper, vilket förbättrar prestandan.
Allmänna metoder
En allmän metod deklarerar sin egen typparameter. Kompilatorn härleder ofta typargumentet från de värden som du skickar, så du behöver inte ange det explicit:
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
I anropet Print(42) härleder kompilatorn T som int från argumentet. Du kan skriva Print<int>(42) explicit, men typinferens håller koden renare.
Samlingsuttryck
Samlingsuttryck (C# 12) ger en koncis syntax för att skapa samlingar. Använd hakparenteser i stället för konstruktoranrop eller initieringssyntax:
// 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)}");
Spridningsoperatorn (..) infogar elementen i en samling till en annan, vilket är användbart för att kombinera sekvenser:
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
Samlingsuttryck fungerar med matriser, List<T>, Span<T>, ImmutableArray<T>och alla typer som stöder samlingsverktygets mönster. Fullständig syntaxreferens finns i Samlingsuttryck.
Ordboksinitiering
Du kan initiera ordlistor kortfattat med indexerarinitierare. Den här syntaxen använder hakparenteser för att ange nyckel/värde-par:
Dictionary<string, int> scores = new()
{
["Alice"] = 95,
["Bob"] = 87,
["Carol"] = 92
};
foreach (var (name, score) in scores)
{
Console.WriteLine($"{name}: {score}");
}
Du kan sammanfoga ordlistor genom att kopiera en och tillämpa åsidosättningar:
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
Typbegränsningar
Begränsningar begränsar vilka typargument som en allmän typ eller metod accepterar. Med begränsningar kan du anropa metoder eller komma åt egenskaper för typparametern som inte skulle vara tillgängliga på enbart object.
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
De vanligaste begränsningarna är:
| Begränsning | Innebörd |
|---|---|
where T : class |
T måste vara en referenstyp |
where T : struct |
T måste vara en icke-nullbar värdetyp |
where T : new() |
T måste ha en offentlig parameterlös konstruktor |
where T : BaseClass |
T måste härledas från BaseClass |
where T : IInterface |
T måste implementera IInterface |
Du kan kombinera begränsningar: where T : class, IComparable<T>, new(). Mindre vanliga begränsningar är where T : System.Enum, where T : System.Delegateoch where T : unmanaged för specialiserade scenarier. Den fullständiga listan finns i Begränsningar för typparametrar.
Kovarians och kontravarians
Kovarians och kontravarians beskriver hur generiska typer beter sig med arv. De avgör om du kan använda ett argument av mer härledd eller mindre härledd typ än vad som ursprungligen angavs:
// 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"));
-
Covariance (
out T): EnIEnumerable<Dog>kan användas därIEnumerable<Animal>förväntas eftersomDoghärleds frånAnimal. Nyckelordetoutför typparametern aktiverar detta. Parametrar av typen Covariant kan bara visas i utdatapositioner (returtyper). -
Contravariance (
in T): EnAction<Animal>kan användas därAction<Dog>förväntas eftersom alla åtgärder som hanterarAnimalockså kan hanteraDog. Nyckelordetinaktiverar detta. Parametrar av typen Contravariant kan bara visas i indatapositioner (parametrar).
Många inbyggda gränssnitt och delegeringar är redan variant: IEnumerable<out T>, IReadOnlyList<out T>, Func<out TResult>, och Action<in T>. Du drar nytta av variansen automatiskt när du arbetar med dessa typer. En djupgående behandling av utformning av varianter av gränssnitt och delegater finns i Covariance och contravariance.
Skapa egna generiska typer
Du kan definiera dina egna allmänna klasser, structs, gränssnitt och metoder. I följande exempel visas en enkel allmän länkad lista som illustration. I praktiken använder du List<T> eller någon annan inbyggd samling:
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
Generiska typer är inte begränsade till klasser. Du kan definiera generiska interface, structoch record typer. Mer information om hur du utformar generiska algoritmer och komplexa begränsningskombinationer finns i Generics i .NET.