Sdílet prostřednictvím


Obecné typy a metody

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án IEnumerable<Animal>, protože Dog je odvozen z Animal. Klíčové out slovo 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): Lze Action<Animal> použít tam, kde Action<Dog> je očekáván, protože jakákoli akce, která zpracovává Animal , může také zpracovat Dog. Klíčové in slovo 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.

Viz také