Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Wskazówka
Dopiero zaczynasz programować oprogramowanie? Najpierw zacznij od samouczków Wprowadzenie . Napotkasz typy ogólne zaraz po użyciu kolekcji, takich jak List<T>.
Czy masz doświadczenie w pracy w innym języku? Typy generyczne w języku C# są podobne do tych w Javie lub szablonach C++, ale z pełną informacją o typach w czasie wykonywania i bez wymazywania typów. Przejrzyj sekcje dotyczące wyrażeń kolekcji oraz kowariancji i kontrawariancji, aby zidentyfikować wzorce specyficzne dla języka C#.
Szablony typów umożliwiają pisanie kodu, który działa z dowolnym typem przy pełnym zachowaniu typowania. Zamiast pisać oddzielne klasy lub metody dla int, stringi każdego innego typu, należy napisać jedną wersję z co najmniej jednym parametrem typu (takimi jak T, lub TKey i TValue) i określić rzeczywiste typy podczas korzystania z niego. Kompilator sprawdza typy w czasie kompilacji, więc nie potrzebujesz rzutów środowiska uruchomieniowego ani ryzyka InvalidCastException.
Spotykasz rodzajowe stale w codziennym języku C#. Kolekcje, typy asynchronicznych wyników, delegaty i LINQ opierają się na typach ogólnych.
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)}");
W każdym przypadku argument typu w nawiasach kątowych (<int>, <string>, <Product>) informuje typ ogólny, jakiego rodzaju dane przechowuje lub na których działa. Kompilator wymusza bezpieczeństwo typu. Nie można przypadkowo dodać elementu string do elementu List<int>.
Korzystanie z typów ogólnych
Częściej korzystasz z typów ogólnych z biblioteki klas .NET zamiast tworzyć własne. W poniższych sekcjach przedstawiono najbardziej typowe typy ogólne, których będziesz używać.
Kolekcje ogólne
System.Collections.Generic Przestrzeń nazw zapewnia klasy kolekcji zapewniające bezpieczeństwo typów. Zawsze używaj tych kolekcji zamiast kolekcji niegenerycznych, takich jak 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
Kolekcje ogólne uniemożliwiają błędy typu w czasie wykonywania, ponieważ zamiast tego występują błędy w czasie kompilacji. Te kolekcje unikają również tworzenia pól dla typów wartości, co zwiększa wydajność.
Metody ogólne
Metoda ogólna deklaruje własny parametr typu. Kompilator często wywnioskuje argument typu z przekazanych wartości, więc nie trzeba ich jawnie określać:
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
W wywołaniu Print(42) kompilator wnioskuje T jako int z argumentu. Możesz napisać Print<int>(42) jawnie, ale wnioskowanie typu sprawia, że kod jest czytelniejszy.
Wyrażenia kolekcji
Wyrażenia kolekcji (C# 12) zapewniają zwięzłą składnię tworzenia kolekcji. Użyj nawiasów kwadratowych zamiast wywołań konstruktora lub składni inicjatora:
// 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)}");
Operator spreadu (..) wskazuje elementy jednej kolekcji na drugą, co jest przydatne do łączenia sekwencji:
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
Wyrażenia kolekcji działają z tablicami, List<T>, Span<T>, ImmutableArray<T>i dowolnym typem obsługującym wzorzec konstruktora kolekcji. Aby uzyskać pełne odniesienie do składni, zobacz Wyrażenia kolekcji.
Inicjowanie słownika
Słowniki można zainicjować zwięźle za pomocą inicjalizatorów indeksatorów. Składnia ta używa nawiasów kwadratowych do definiowania par klucz-wartość.
Dictionary<string, int> scores = new()
{
["Alice"] = 95,
["Bob"] = 87,
["Carol"] = 92
};
foreach (var (name, score) in scores)
{
Console.WriteLine($"{name}: {score}");
}
Słowniki można scalić, kopiując jeden i stosując przesłonięcia:
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
Ograniczenia typu
Ograniczenia ograniczają, które argumenty typu akceptuje typ ogólny lub metoda. Ograniczenia umożliwiają wywoływanie metod lub uzyskiwanie dostępu do właściwości parametru typu, które nie byłyby dostępne tylko za pomocą 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
Najbardziej typowe ograniczenia to:
| Ograniczenie | Meaning |
|---|---|
where T : class |
T musi być typem odwołania |
where T : struct |
T musi być typem wartości nienullowalnym |
where T : new() |
T musi mieć publiczny konstruktor bez parametrów |
where T : BaseClass |
T musi pochodzić z BaseClass |
where T : IInterface |
T musi implementować IInterface |
Ograniczenia można łączyć: where T : class, IComparable<T>, new(). Mniej typowe ograniczenia obejmują where T : System.Enum, where T : System.Delegatei where T : unmanaged dla wyspecjalizowanych scenariuszy. Aby uzyskać pełną listę, zobacz Ograniczenia dotyczące parametrów typu.
Kowariancja i kontrawariancja
Kowariancja i kontrawariancja opisują zachowanie typów ogólnych z dziedziczeniem. Określają, czy można użyć bardziej pochodnego lub mniej pochodnego argumentu typu niż pierwotnie określony:
// 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"));
-
Kowariancja (
out T):IEnumerable<Animal>można użyć tam, gdzie oczekiwane jestIEnumerable<Dog>, ponieważDogpochodzi zAnimal. Słowooutkluczowe w parametrze typu umożliwia to. Kowariantne parametry typu mogą występować tylko w miejscach zwracania (typach zwracanych). -
Kontrawariancja (
in T):Action<Animal>można użyć tam, gdzieAction<Dog>jest oczekiwana, ponieważ każda obsługująca akcjaAnimalmoże również obsługiwaćDog. Słowoinkluczowe umożliwia to. Kontrawariantne parametry typu mogą być wyświetlane tylko w pozycjach wejściowych (parametrach).
Wiele wbudowanych interfejsów i delegatów jest już wariantami: IEnumerable<out T>, IReadOnlyList<out T>, Func<out TResult>, i Action<in T>. Podczas pracy z tymi typami korzystasz automatycznie z wariancji. Aby zapoznać się ze szczegółowym traktowaniem projektowania interfejsów wariantów i delegatów, zobacz Kowariancja i kontrawariancja.
Tworzenie własnych typów ogólnych
Możesz zdefiniować własne klasy ogólne, struktury, interfejsy i metody. W poniższym przykładzie przedstawiono prostą ogólną listę połączoną na potrzeby ilustracji. W praktyce użyj List<T> lub innej wbudowanej kolekcji:
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
Typy ogólne nie są ograniczone do klas. Można zdefiniować typy ogólne interface, structi record . Aby uzyskać więcej informacji na temat projektowania ogólnych algorytmów i złożonych kombinacji ograniczeń, zobacz Generics w .NET.