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í . |
where T : allows ref struct |
Toto anti-constraint deklaruje, že argument typu může T být ref struct typ. Obecný typ nebo metoda musí dodržovat pravidla bezpečnosti ref pro libovolnou ref struct instanci, protože T může být . |
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
struct
omezení ,class
,class?
,notnull
aunmanaged
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
nebowhere T : Base?
) nelze kombinovat s žádnou z omezenístruct
, ,class
,class?
,notnull
nebounmanaged
. - 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 sstruct
omezením.unmanaged
Pokud zadátenew()
omezení, musí to být poslední omezení pro tento parametr typu. Anti-omezení, pokud je to možné, může postupovat podlenew()
omezení. - Omezení
default
lze použít pouze u implementací přepsání nebo explicitního rozhraní. Nejde ho kombinovat s omezeními aniclass
s omezenímistruct
. - Anti-constraint
allows ref struct
nelze kombinovat sclass
omezením neboclass?
omezením. - Anti-constraint
allows ref struct
musí dodržovat všechna omezení pro tento parametr typu.
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 : notnull, Employee, IComparable<T>, new()
{
// ...
public void AddDefault()
{
T t = new T();
// ...
}
}
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
null
nevá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 struct
class
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 ani class
struct
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;
Předchozí metodu můžete použít ke kombinování delegátů se stejným typem:
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, nekompiluje se. 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>
{
static abstract T operator +(T left, T right);
static abstract 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>
{
static abstract IAdditionSubtraction<T> operator +(
IAdditionSubtraction<T> left,
IAdditionSubtraction<T> right);
static abstract 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í.
Umožňuje strukturu ref.
Anti-constraint allows ref struct
deklaruje, že odpovídající argument typu může být ref struct
typ. Instance tohoto parametru typu musí dodržovat následující pravidla:
- Nedá se to za boxovat.
- Podílí se na bezpečnostních pravidlech ref.
- Instance se nedají použít, pokud
ref struct
typ není povolený, napříkladstatic
pole. - Instance lze označit modifikátorem
scoped
.
Klauzule allows ref struct
není zděděna. V následujícím kódu:
class SomeClass<T, S>
where T : allows ref struct
where S : T
{
// etc
}
Argumentem nemůže býtref struct
, S
protože S
klauzule neobsahujeallows ref struct
.
Parametr typu, který má allows ref struct
klauzuli, nelze použít jako argument typu, pokud odpovídající parametr typu také klauzuli allows ref struct
neobsahuje. Toto pravidlo je znázorněno v následujícím příkladu:
public class Allow<T> where T : allows ref struct
{
}
public class Disallow<T>
{
}
public class Example<T> where T : allows ref struct
{
private Allow<T> fieldOne; // Allowed. T is allowed to be a ref struct
private Disallow<T> fieldTwo; // Error. T is not allowed to be a ref struct
}
Předchozí ukázka ukazuje, že argument typu, který může být typem ref struct
, nelze nahradit parametrem typu, který nemůže být typem ref struct
.