Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Návod
Začínáte s vývojem softwaru? Začněte nejprve kurzy Začínáme . Narazíte na obecné typy, jakmile použijete kolekce jako List<T>.
Máte zkušenosti v jiném jazyce? Obecné typy jazyka C# jsou podobné obecným typům v Java nebo šablonách v jazyce C++, ale s úplnými informacemi o typu runtime a bez vymazání typu. Přeskočte výrazy kolekce a oddílykovariance a kontravariance pro vzory specifické pro jazyk C#.
Obecné typy umožňují psát kód, který funguje s libovolným typem a současně zachovává plnou bezpečnost typů. Místo psaní samostatných tříd nebo metod pro int, stringa každý druhý typ, který potřebujete, napište jednu verzi s jedním nebo více parametry typu (například T, nebo TKey a TValue) a zadejte skutečné typy při jeho použití. Kompilátor kontroluje typy v době kompilace, takže nepotřebujete přetypování za běhu programu ani se nevystavujete rizikům InvalidCastException.
V jazyce C# se neustále setkáte s obecnými typy. Kolekce, asynchronní návratové typy, delegáty a LINQ spoléhají na obecné typy:
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)}");
V každém případě argument typu v hranatých závorkách (<int>, <string>, <Product>) říká obecnému typu, jaký druh dat uchovává nebo pracuje. Kompilátor vynucuje bezpečnost typů. Nemůžete omylem přidat string k List<int>.
Využívání obecných typů
Častěji využíváte obecné typy z knihovny tříd .NET, než abyste vytvářeli vlastní. V následujících částech najdete nejběžnější obecné typy, které budete používat.
Obecné kolekce
Prostor názvů System.Collections.Generic poskytuje typově bezpečné třídy pro kolekce. Vždy používejte tyto kolekce místo negenerických kolekcí, například 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
Obecné kolekce brání chybám typu za běhu, protože místo toho dochází k chybám v době kompilace. Tyto kolekce se také vyhýbají boxing u hodnotových typů, což zvyšuje výkon.
Obecné metody
Obecná metoda deklaruje vlastní parametr typu. Kompilátor často odvodí argument typu z předávaných hodnot, takže ho nemusíte explicitně zadávat:
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
Ve volání Print(42)kompilátor odvodí T z int argumentu. Můžete psát Print<int>(42) explicitně, ale odvození typu udržuje kód čistější.
Výrazy kolekce
Výrazy kolekce (C# 12) poskytují stručnou syntaxi pro vytváření kolekcí. Místo volání konstruktoru nebo syntaxe inicializátoru použijte hranaté závorky:
// 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)}");
Operátor rozprostření (..) vloží prvky jedné kolekce do druhé, což je užitečné pro kombinování sekvencí.
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
Výrazy kolekce pracují s poli, List<T>, Span<T>, ImmutableArray<T>, a libovolným typem, který podporuje vzor tvůrce kolekcí. Úplný přehled syntaxe najdete v části Výrazy kolekce.
Inicializace slovníku
Slovníky můžete inicializovat výstižně pomocí inicializátorů indexeru. Tato syntaxe používá hranaté závorky k nastavení párů klíč-hodnota:
Dictionary<string, int> scores = new()
{
["Alice"] = 95,
["Bob"] = 87,
["Carol"] = 92
};
foreach (var (name, score) in scores)
{
Console.WriteLine($"{name}: {score}");
}
Slovníky můžete sloučit zkopírováním jednoho a použitím výjimek:
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
Omezení typů
Omezení omezují, které argumenty typu obecný typ nebo metoda přijímá. Omezení umožňují volat metody nebo přistoupit k vlastnostem parametru typu, které by nebyly dostupné na object samostatně.
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
Nejběžnější omezení jsou:
| Omezení | Význam |
|---|---|
where T : class |
T musí být referenčním typem. |
where T : struct |
T musí být typ hodnoty, který nesmí obsahovat hodnotu null. |
where T : new() |
T musí mít veřejný konstruktor bez parametrů. |
where T : BaseClass |
T musí být odvozeno z BaseClass |
where T : IInterface |
T musí implementovat IInterface |
Omezení můžete kombinovat: where T : class, IComparable<T>, new(). Méně běžná omezení zahrnují where T : System.Enum, where T : System.Delegatea where T : unmanaged pro specializované scénáře. Úplný seznam najdete v tématu Omezení parametrů typu.
Kovariance a kontravariance
Kovariance a kontravariance popisují, jak se obecné typy chovají s dědičností. Určí, jestli můžete použít odvozenější nebo méně odvozený argument typu, než byl původně zadán:
// 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"));
-
Kovariance (
out T):IEnumerable<Dog>lze použít tam, kde je očekávánIEnumerable<Animal>, protožeDogje odvozen zAnimal. Klíčovéoutslovo u parametru typu to umožňuje. Kovariantní parametry typu se mohou objevit pouze ve výstupních pozicích (návratové typy). -
Kontravariance (
in T): LzeAction<Animal>použít tam, kdeAction<Dog>je očekáván, protože jakákoli akce, která zpracováváAnimal, může také zpracovatDog. Klíčovéinslovo to umožňuje. Parametry kontravariantního typu se můžou objevit pouze ve vstupních pozicích (parametrech).
Mnoho předdefinovaných rozhraní a delegátů je již variantou: IEnumerable<out T>, IReadOnlyList<out T>, Func<out TResult>, a Action<in T>. Při práci s těmito typy můžete automaticky využívat odchylky. Podrobné zpracování návrhu variantních rozhraní a delegátů najdete v tématu Kovariance a kontravariance.
Vytvoření vlastních obecných typů
Můžete definovat vlastní obecné třídy, struktury, rozhraní a metody. Následující příklad ukazuje jednoduchý obecný propojený seznam pro ilustraci. V praxi použijte List<T> nebo použijte jinou integrovanou kolekci:
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
Obecné typy nejsou omezeny na třídy. Můžete definovat obecné typy interface, struct a record. Další informace o navrhování obecných algoritmů a složitých kombinací omezení najdete v tématu Generics v .NET.