Omezení parametrů typu (Průvodce programováním v C#)

Omezení informují kompilátor o schopnostech, které musí mít argument typu. Bez jakýchkoli omezení může být argument typu libovolný typ. Kompilátor může předpokládat pouze členy System.Object, což je konečná základní třída pro libovolný typ .NET. Další informace naleznete v tématu Proč používat omezení. Pokud kód klienta používá typ, který nevyhovuje omezení, kompilátor vydá chybu. Omezení jsou určena pomocí kontextového klíčového where slova. Následující tabulka uvádí různé typy omezení:

Omezení Popis
where T : struct Argument typu musí být nenulový typ hodnoty, který zahrnuje record struct typy. Informace o typech hodnot s možnou hodnotou null naleznete v tématu Typy hodnot s možnou hodnotou Null. Vzhledem k tomu, že všechny typy hodnot mají přístupný konstruktor bez parametrů, deklarovaný nebo implicitní, struct omezení implikuje new() omezení a nelze ho new() kombinovat s omezením. Omezení nemůžete kombinovat struct s unmanaged omezením.
where T : class Argument typu musí být referenčním typem. Toto omezení platí také pro libovolnou třídu, rozhraní, delegáta nebo typ pole. V kontextu s možnou hodnotou null musí být odkazový typ bez T hodnoty null.
where T : class? Argument typu musí být referenčním typem, buď s možnou hodnotou null, nebo nenulovou hodnotou. Toto omezení platí také pro všechny třídy, rozhraní, delegáty nebo typ pole, včetně záznamů.
where T : notnull Argument typu musí být nenulový. Argumentem může být nenulový odkazový typ nebo nenulový typ hodnoty.
where T : unmanaged Argument typu musí být nespravovatelný nespravovaný typ. Omezení unmanaged znamená struct omezení a nelze ho kombinovat s omezeními ani new() s omezenímistruct.
where T : new() Argument typu musí mít veřejný konstruktor bez parametrů. Při použití společně s jinými omezeními new() musí být omezení zadáno jako poslední. Omezení new() nelze kombinovat s omezenímistruct.unmanaged
where T :<Název základní třídy> Argument typu musí být nebo musí být odvozen ze zadané základní třídy. V kontextu s možnou hodnotou null musí být odkazový typ bez T hodnoty null odvozený ze zadané základní třídy.
where T :<Název> základní třídy? Argument typu musí být nebo musí být odvozen ze zadané základní třídy. V kontextu s T možnou hodnotou null může být buď typ s možnou hodnotou null, nebo nenulový odvozený ze zadané základní třídy.
where T :<název rozhraní> Argument typu musí být nebo implementovat zadané rozhraní. Lze zadat více omezení rozhraní. Rozhraní omezení může být také obecné. V kontextu s možnou hodnotou null musí být nenulový typ, T který implementuje zadané rozhraní.
where T :<název> rozhraní? Argument typu musí být nebo implementovat zadané rozhraní. Lze zadat více omezení rozhraní. Rozhraní omezení může být také obecné. V kontextu s možnou hodnotou null může být typ odkazu s T možnou hodnotou null, nenulový odkaz nebo typ hodnoty. T nemůže být typ hodnoty s možnou hodnotou null.
where T : U Argument typu zadaný pro T musí být nebo odvozen z argumentu zadaného pro U. V kontextu s možnou hodnotou null, pokud U je nenulový odkazový typ, T musí být nenulový odkazový typ. Pokud U je odkazový typ s možnou hodnotou null, T může být nullable nebo non-nullable.
where T : default Toto omezení vyřeší nejednoznačnost v případě, že při přepsání metody nebo poskytnutí explicitní implementace rozhraní potřebujete zadat parametr nekontrénovaného typu. Omezení default znamená základní metodu bez class omezení nebo struct omezení. Další informace najdete v návrhu default specifikace omezení .

Některá omezení se vzájemně vylučují a některá omezení musí být v zadaném pořadí:

  • Můžete použít maximálně jednu z structomezení , class, class?, notnulla unmanaged omezení. Pokud zadáte některá z těchto omezení, musí to být první omezení zadané pro tento parametr typu.
  • Omezení základní třídy (where T : Base nebo where T : Base?) nelze kombinovat s žádnou z omezení struct, , class, class?, notnullnebo unmanaged.
  • Omezení základní třídy můžete použít maximálně v jednom formuláři. Pokud chcete podporovat základní typ s možnou hodnotou null, použijte Base?.
  • Nelze pojmenovat nenulovou i nulovou formu rozhraní jako omezení.
  • Omezení new() nelze kombinovat s struct omezením.unmanaged Pokud zadáte new() omezení, musí to být poslední omezení pro tento parametr typu.
  • Omezení default lze použít pouze u implementací přepsání nebo explicitního rozhraní. Nejde ho kombinovat s omezeními ani class s omezenímistruct.

Proč používat omezení

Omezení určují možnosti a očekávání parametru typu. Deklarace těchto omezení znamená, že můžete použít operace a volání metody typu omezení. Omezení použijete u parametru typu, pokud obecná třída nebo metoda používá jakoukoli operaci s obecnými členy nad rámec jednoduchého přiřazení, což zahrnuje volání všech metod, které nejsou podporovány System.Object. Omezení základní třídy například kompilátoru říká, že argument typu může nahradit pouze objekty tohoto typu nebo odvozené z tohoto typu. Jakmile má kompilátor tuto záruku, může povolit, aby metody tohoto typu byly volána v obecné třídě. Následující příklad kódu ukazuje funkce, které můžete přidat do GenericList<T> třídy (v úvodu do obecných typů) použitím omezení základní třídy.

public class Employee
{
    public Employee(string name, int id) => (Name, ID) = (name, id);
    public string Name { get; set; }
    public int ID { get; set; }
}

public class GenericList<T> where T : Employee
{
    private class Node
    {
        public Node(T t) => (Next, Data) = (null, t);

        public Node? Next { get; set; }
        public T Data { get; set; }
    }

    private Node? head;

    public void AddHead(T t)
    {
        Node n = new Node(t) { Next = head };
        head = n;
    }

    public IEnumerator<T> GetEnumerator()
    {
        Node? current = head;

        while (current != null)
        {
            yield return current.Data;
            current = current.Next;
        }
    }

    public T? FindFirstOccurrence(string s)
    {
        Node? current = head;
        T? t = null;

        while (current != null)
        {
            //The constraint enables access to the Name property.
            if (current.Data.Name == s)
            {
                t = current.Data;
                break;
            }
            else
            {
                current = current.Next;
            }
        }
        return t;
    }
}

Omezení umožňuje obecné třídě používat Employee.Name vlastnost. Omezení určuje, že všechny položky typu T jsou zaručeny buď Employee objekt, nebo objekt, který dědí z Employee.

U stejného parametru typu se dá použít více omezení a samotná omezení můžou být obecná, jak je znázorněno níže:

class EmployeeList<T> where T : Employee, System.Collections.Generic.IList<T>, IDisposable, new()
{
    // ...
}

Při použití where T : class omezení nepoužívejte operátory == parametru typu a != operátory, protože tyto operátory testují pouze referenční identitu, nikoli rovnost hodnot. K tomuto chování dochází i v případě, že jsou tyto operátory přetíženy v typu, který se používá jako argument. Následující kód ukazuje tento bod; výstup je false, i když String třída přetěžuje == operátor.

public static void OpEqualsTest<T>(T s, T t) where T : class
{
    System.Console.WriteLine(s == t);
}

private static void TestStringEquality()
{
    string s1 = "target";
    System.Text.StringBuilder sb = new System.Text.StringBuilder("target");
    string s2 = sb.ToString();
    OpEqualsTest<string>(s1, s2);
}

Kompilátor pouze ví, že T je referenčním typem v době kompilace a musí používat výchozí operátory platné pro všechny odkazové typy. Pokud je nutné otestovat rovnost hodnot, použijte where T : IEquatable<T> nebo where T : IComparable<T> omezení a implementujte rozhraní v libovolné třídě použité k vytvoření obecné třídy.

Omezení více parametrů

Omezení můžete použít na více parametrů a několik omezení na jeden parametr, jak je znázorněno v následujícím příkladu:

class Base { }
class Test<T, U>
    where U : struct
    where T : Base, new()
{ }

Parametry nevázaného typu

Parametry typu, které nemají žádná omezení, například T ve veřejné třídě SampleClass<T>{}, se nazývají nevázané parametry typu. Parametry nevázaného typu mají následující pravidla:

  • Operátory != a == operátory nelze použít, protože neexistuje žádná záruka, že argument konkrétního typu tyto operátory podporuje.
  • Dají se převést na jakýkoli System.Object typ rozhraní nebo z nich nebo je explicitně převést.
  • Můžete je porovnat s hodnotou null. Pokud je porovnáván nullnevázaný parametr , porovnání vždy vrátí hodnotu false, pokud je argument typu hodnota typ.

Parametry typu jako omezení

Použití parametru obecného typu jako omezení je užitečné, když členová funkce s vlastním parametrem typu musí tento parametr omezit na parametr typu obsahujícího typu, jak je znázorněno v následujícím příkladu:

public class List<T>
{
    public void Add<U>(List<U> items) where U : T {/*...*/}
}

V předchozím příkladu T je omezení typu v kontextu Add metody a nevázaný parametr typu v kontextu List třídy.

Parametry typu lze také použít jako omezení v definicích obecných tříd. Parametr typu musí být deklarován v hranatých závorkách spolu s jinými parametry typu:

//Type parameter V is used as a type constraint.
public class SampleClass<T, U, V> where T : V { }

Užitečnost parametrů typu jako omezení s obecnými třídami je omezena, protože kompilátor nemůže předpokládat nic o parametru typu s tím rozdílem, že je odvozen z System.Object. Parametry typu použijte jako omezení obecných tříd ve scénářích, ve kterých chcete vynutit vztah dědičnosti mezi dvěma parametry typu.

notnull Omezení

Pomocí omezení můžete notnull určit, že argument typu musí být typ hodnoty s možnou hodnotou null nebo odkazový typ bez hodnoty null. Na rozdíl od většiny ostatních omezení, pokud argument typu porušuje notnull omezení, kompilátor místo chyby vygeneruje upozornění.

Omezení notnull má účinek pouze v případech, kdy se používá v kontextu s možnou hodnotou null. Pokud omezení přidáte notnull v nezapomnělém kontextu s možnou hodnotou null, kompilátor nevygeneruje žádná upozornění ani chyby pro porušení omezení.

class Omezení

Omezení class v kontextu s možnou hodnotou null určuje, že argument typu musí být nenulový odkazový typ. Pokud je argument typu v kontextu s možnou hodnotou null, kompilátor vygeneruje upozornění.

default Omezení

Přidání odkazových typů s možnou hodnotou null komplikuje použití T? v obecném typu nebo metodě. T? lze použít buď s structclass omezením, nebo s omezením, ale jedna z nich musí být přítomna. class Při použití omezení odkazovat T? na typ odkazu s možnou hodnotou null pro T. T? lze použít, pokud není použito žádné omezení. V takovém případě T? se interpretuje jako T? pro typy hodnot a odkazové typy. Pokud T je však instance Nullable<T>, T? je stejná jako T. Jinými slovy, nestalo se T??to .

Vzhledem k tomu T? , že lze nyní použít bez class omezení nebo struct omezení, může dojít k nejednoznačnostem v přepsání nebo explicitních implementacích rozhraní. V obou těchto případech přepsání neobsahuje omezení, ale dědí je ze základní třídy. Pokud základní třída nepoužije buď class omezení nebo struct omezení, odvozené třídy musí nějak určit přepsání platí pro základní metodu bez omezení. Odvozená metoda použije default omezení. Omezení default objasňuje aniclassstruct omezení.

Nespravované omezení

Pomocí omezení můžete unmanaged určit, že parametr typu musí být nespravovatelným nespravovaným typem. Omezení unmanaged umožňuje psát opakovaně použitelné rutiny pro práci s typy, které lze manipulovat jako bloky paměti, jak je znázorněno v následujícím příkladu:

unsafe public static byte[] ToByteArray<T>(this T argument) where T : unmanaged
{
    var size = sizeof(T);
    var result = new Byte[size];
    Byte* p = (byte*)&argument;
    for (var i = 0; i < size; i++)
        result[i] = *p++;
    return result;
}

Předchozí metoda musí být zkompilována v unsafe kontextu, protože používá sizeof operátor u typu, který není známý jako předdefinovaný typ. unmanaged Bez omezení sizeof není operátor k dispozici.

Omezení unmanaged znamená struct omezení a nedá se s ním kombinovat. struct Vzhledem k tomu, že omezení implikuje new() omezení, unmanaged omezení se nedá kombinovat s new() omezením.

Delegování omezení

Můžete použít System.Delegate omezení základní třídy nebo System.MulticastDelegate jako omezení základní třídy. CLR toto omezení vždy povolil, ale jazyk C# ho nepovolil. Toto System.Delegate omezení umožňuje psát kód, který funguje s delegáty způsobem bezpečným způsobem. Následující kód definuje rozšiřující metodu, která kombinuje dva delegáty za předpokladu, že jsou stejného typu:

public static TDelegate? TypeSafeCombine<TDelegate>(this TDelegate source, TDelegate target)
    where TDelegate : System.Delegate
    => Delegate.Combine(source, target) as TDelegate;

Výše uvedenou metodu můžete použít ke kombinování delegátů, kteří mají stejný typ:

Action first = () => Console.WriteLine("this");
Action second = () => Console.WriteLine("that");

var combined = first.TypeSafeCombine(second);
combined!();

Func<bool> test = () => true;
// Combine signature ensures combined delegates must
// have the same type.
//var badCombined = first.TypeSafeCombine(test);

Pokud odkomentujete poslední řádek, nebude se zkompilovat. Oba first a test jsou typy delegátů, ale jsou to různé typy delegátů.

Omezení výčtu

Typ můžete také zadat System.Enum jako omezení základní třídy. CLR toto omezení vždy povolil, ale jazyk C# ho nepovolil. Generics using System.Enum provide type-safe programming to cache results from using the static methods in System.Enum. Následující ukázka najde všechny platné hodnoty pro typ výčtu a pak vytvoří slovník, který tyto hodnoty mapuje na řetězcovou reprezentaci.

public static Dictionary<int, string> EnumNamedValues<T>() where T : System.Enum
{
    var result = new Dictionary<int, string>();
    var values = Enum.GetValues(typeof(T));

    foreach (int item in values)
        result.Add(item, Enum.GetName(typeof(T), item)!);
    return result;
}

Enum.GetValues a Enum.GetName použijte reflexi, která má vliv na výkon. Můžete volat EnumNamedValues sestavení kolekce, která je uložena v mezipaměti a znovu použita, a neopakovat volání, která vyžadují reflexi.

Můžete ho použít, jak je znázorněno v následující ukázce, k vytvoření výčtu a vytvoření slovníku jeho hodnot a názvů:

enum Rainbow
{
    Red,
    Orange,
    Yellow,
    Green,
    Blue,
    Indigo,
    Violet
}
var map = EnumNamedValues<Rainbow>();

foreach (var pair in map)
    Console.WriteLine($"{pair.Key}:\t{pair.Value}");

Implementujte deklarované rozhraní argumentů typu.

Některé scénáře vyžadují, aby argument zadaný pro parametr typu implementovali toto rozhraní. Příklad:

public interface IAdditionSubtraction<T> where T : IAdditionSubtraction<T>
{
    public abstract static T operator +(T left, T right);
    public abstract static T operator -(T left, T right);
}

Tento model umožňuje kompilátoru jazyka C# určit typ obsahující pro přetížené operátory nebo jakoukoli metodu nebo static abstract metodustatic virtual. Poskytuje syntaxi, aby operátory sčítání a odčítání mohly být definovány u typu obsahujícího. Bez tohoto omezení by se parametry a argumenty musely deklarovat jako rozhraní, nikoli jako parametr typu:

public interface IAdditionSubtraction<T> where T : IAdditionSubtraction<T>
{
    public abstract static IAdditionSubtraction<T> operator +(
        IAdditionSubtraction<T> left,
        IAdditionSubtraction<T> right);

    public abstract static IAdditionSubtraction<T> operator -(
        IAdditionSubtraction<T> left,
        IAdditionSubtraction<T> right);
}

Předchozí syntaxe by vyžadovala, aby implementátory pro tyto metody používaly explicitní implementaci rozhraní. Poskytnutí dalšího omezení umožňuje rozhraní definovat operátory z hlediska parametrů typu. Typy, které implementují rozhraní, mohou implicitně implementovat metody rozhraní.

Viz také