Omezení parametrů typu (Průvodce programováním v C#)
Omezení informují kompilátor o možnostech, 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 třídy , což je základní třída pro System.Object libovolný typ rozhraní .NET. Další informace najdete v tématu Proč používat omezení. Pokud klientský kód používá typ, který nesplňuje omezení, kompilátor vygeneruje chybu. Omezení jsou určená pomocí where kontextového klíčového slova . Následující tabulka uvádí různé typy omezení:
| Omezení | Popis |
|---|---|
where T : struct |
Argument typu musí být hodnotový typ, který nemůže mít hodnotu null. Informace o hodnotových typech s možnou hodnotou null najdete v tématu Typy hodnot s možnou hodnotou Null. Vzhledem k tomu, že všechny typy hodnot mají přístupný konstruktor bez parametrů, omezení implikuje omezení a nelze ho struct new() kombinovat s new() omezením. Toto omezení nelze struct kombinovat s unmanaged omezením. |
where T : class |
Argument typu musí být odkazový typ. Toto omezení platí také pro všechny typy tříd, rozhraní, delegátů nebo polí. V kontextu s možnou hodnotou null v jazyce C# 8.0 nebo novějším musí být odkazový typ s možnou T hodnotou null. |
where T : class? |
Argument typu musí být odkazový typ, který může mít hodnotu null nebo nemůže mít hodnotu null. Toto omezení platí také pro všechny typy tříd, rozhraní, delegátů nebo polí. |
where T : notnull |
Argument typu musí být typ, který nemůže mít hodnotu null. Argumentem může být odkazový typ s možnou hodnotou null v jazyce C# 8.0 nebo novějším nebo hodnotový typ, který nemůže mít hodnotu null. |
where T : default |
Toto omezení řeší nejednoznačnost v případě, že při přepsání metody nebo poskytnutí explicitní implementace rozhraní potřebujete zadat parametr typu bez omezení. Omezení default implikuje základní metodu bez omezení class struct nebo . Další informace najdete v návrhu default specifikace omezení. |
where T : unmanaged |
Argument typu musí být nespravovaný typ, který nemůže mít hodnotu null. Omezení implikuje omezení a nelze ho kombinovat s unmanaged struct omezeními struct nebo new() . |
where T : new() |
Argument typu musí mít veřejný konstruktor bez parametrů. Při použití společně s jinými omezeními musí new() být omezení zadáno jako poslední. Omezení new() nelze kombinovat s omezeními struct a unmanaged . |
where T : <base class name> |
Argument typu musí být nebo odvozovat ze zadané základní třídy. V kontextu s možnou hodnotou null v jazyce C# 8.0 a novějším musí být odkazový typ s možnou hodnotou null odvozený T ze zadané základní třídy. |
where T : <base class name>? |
Argument typu musí být nebo odvozovat ze zadané základní třídy. V kontextu s možnou hodnotou null v jazyce C# 8.0 a novějším může být buď typ s možnou hodnotou null nebo typ s možnou hodnotou null odvozený ze T zadané základní třídy. |
where T : <interface name> |
Argument typu musí být nebo implementovat zadané rozhraní. Je možné zadat více omezení rozhraní. Rozhraní s omezením může být také obecné. V kontextu s možnou hodnotou null v jazyce C# 8.0 a novějším musí být typ s možnou hodnotou null, který T implementuje zadané rozhraní. |
where T : <interface name>? |
Argument typu musí být nebo implementovat zadané rozhraní. Je možné zadat více omezení rozhraní. Rozhraní s omezením může být také obecné. V kontextu s možnou hodnotou null v jazyce C# 8.0 může být odkazový typ s možnou hodnotou null, odkazový typ s možnou hodnotou null T nebo typ hodnoty. T nemusí být hodnotový typ s možnou hodnotou null. |
where T : U |
Argument typu zadaný pro T musí být nebo odvozen z argumentu zadaného pro U . Pokud je v kontextu s možnou hodnotou null odkazový typ, který není nullable, musí být odkazový typ s možnou U T hodnotou null. Pokud U je odkaz typu s možnou hodnotou null, může být buď T nullable, nebo non-nullable. |
Proč používat omezení
Omezení určují možnosti a očekávání parametru typu. Deklarování těchto omezení znamená, že můžete použít operace a volání metod typu omezení. Pokud vaše obecná třída nebo metoda používá jakoukoli operaci s obecnými členy nad rámec jednoduchého přiřazení nebo volání metod, které nepodporuje , použijete omezení na System.Object parametr typu. Například omezení základní třídy kompilátoru říká, že jako argumenty typu se budou používat pouze objekty tohoto typu nebo odvozené z tohoto typu. Jakmile má kompilátor tuto záruku, může povolit volání metod tohoto typu v obecné třídě. Následující příklad kódu ukazuje funkce, které můžete přidat do GenericList<T> třídy (v úvodudo 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žít vlastnost Employee.Name . Omezení určuje, že všechny položky typu jsou zaručené jako objekt nebo T Employee objekt, který dědí z Employee .
U stejného parametru typu lze použít více omezení a omezení sama o sobě mohou být obecnými typy, a to následujícím způsobem:
class EmployeeList<T> where T : Employee, IEmployee, System.IComparable<T>, new()
{
// ...
}
Při použití omezení se vyhněte operátorům a u parametru typu , protože tyto operátory budou testovat pouze referenční identitu, nikoli rovnost where T : class == != hodnoty. 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. Tento bod znázorňuje následující kód. Výstup je false, i String když 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 ví pouze, že je odkazový typ v době kompilace, a musí používat výchozí operátory, které jsou T platné pro všechny odkazové typy. Pokud je nutné testovat rovnost hodnoty, doporučeným způsobem je také použít omezení nebo a implementovat rozhraní v jakékoli třídě, která bude použita k vytvoření where T : IEquatable<T> where T : IComparable<T> obecné třídy.
Omezení více parametrů
Omezení můžete použít pro více parametrů a více 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ě , se nazývají parametry typu bez SampleClass<T>{} vazby. Parametry typu Bez vázaného typu mají následující pravidla:
- Operátory a nelze použít, protože neexistuje žádná záruka, že argument konkrétního typu
!===bude tyto operátory podporovat. - Lze je převést na a z
System.Objectnebo explicitně převést na libovolný typ rozhraní. - Můžete je porovnat s hodnotou null. Pokud je parametr bez vázaného připojení porovnán s parametrem , vrátí porovnání vždy hodnotu false, pokud je argument typu
nullhodnotový typ.
Parametry typu jako omezení
Použití parametru obecného typu jako omezení je užitečné v případě, že členská funkce s vlastním parametrem typu musí omezit tento parametr 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 je omezení typu v kontextu metody a parametr typu bez vazby v kontextu T Add 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 společně s další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í obecných tříd je omezená, protože kompilátor nemůže o parametru typu nic předpokládat s tím rozdílem, že je odvozen z System.Object třídy . 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.
Omezení notnull
Počínaje jazykem C# 8.0 můžete pomocí omezení určit, že argument typu musí být hodnotový typ, který nemůže mít hodnotu null, nebo odkazový typ, který nemůže mít hodnotu notnull null. Na rozdíl od většiny ostatních omezení, pokud argument typu porušuje omezení, kompilátor notnull místo chyby vygeneruje upozornění.
Omezení notnull má účinek pouze v případě, že se používá v kontextu s možnou hodnotou null. Pokud přidáte omezení do zapomenulelného kontextu s možnou hodnotou null, kompilátor negeneruje žádná upozornění ani chyby pro porušení notnull omezení.
Omezení class
Počínaje jazykem C# 8.0 určuje omezení v kontextu s možnou hodnotou null, že argument typu musí být odkazový typ, který nemůže mít hodnotu class null. Pokud je v kontextu s možnou hodnotou null argument typu odkaz s možnou hodnotou null, kompilátor vygeneruje upozornění.
Omezení default
Přidání odkazových typů s možnou hodnotou null komplikuje použití v T? obecném typu nebo metodě. Před jazykem C# 8 bylo možné použít pouze v případě, T? že struct se omezení použilo na T . V tomto kontextu T? odkazuje na typ pro Nullable<T> T . Počínaje jazykem C# 8 je možné použít s omezením nebo , ale musí být k T? struct dispozici jeden z class nich. Při použití omezení se odkazuje na odkaz na odkaz s možnou class T? hodnotou null pro T . Počínaje jazykem C# 9 T? lze použít, pokud není použito žádné omezení. V takovém případě je T? interpretován stejně jako v jazyce C# 8 pro typy hodnot a odkazové typy. Pokud je T však instance , je stejná jako Nullable<T> T? T . Jinými slovy se z něj ne stane T?? .
Vzhledem k tomu, že je nyní možné použít bez omezení nebo , mohou nejednoznačnosti nastat v T? class struct přepsáních nebo explicitních implementacích rozhraní. V obou těchto případech přepsání nezahrnuje omezení, ale dědí je ze základní třídy. Pokud základní třída nepoužije omezení ani , odvozené třídy musí nějakým způsobem určit přepsání, které se použije na základní metodu bez class struct jakéhokoli omezení. V tomto případě použije odvozená metoda default omezení. Toto default omezení nevyjasňuje ani class struct omezení.
Nespravované omezení
Počínaje jazykem C# 7.3 můžete pomocí omezení určit, že parametr typu musí být nespravovaný typ, který nemůže mít hodnotu unmanaged null. Toto omezení umožňuje zapisovat znovu použitelné rutiny pro práci s typy, které lze manipulovat jako s bloky paměti, jak je znázorněno v unmanaged 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 kontextu, protože používá operátor pro typ, o které není známo, unsafe sizeof že je předdefinovaný typ. Bez unmanaged tohoto omezení není sizeof operátor k dispozici.
Omezení implikuje omezení a nelze s ním unmanaged struct kombinovat. Vzhledem k struct tomu, že omezení implikuje omezení, nelze omezení kombinovat ani s new() unmanaged new() omezením.
Omezení delegáta
Od jazyka C# 7.3 můžete také použít nebo jako omezení System.Delegate System.MulticastDelegate základní třídy. Modul CLR toto omezení vždycky povolené, ale jazyk C# ho nepovolil. Toto omezení umožňuje psát kód, který pracuje s System.Delegate delegáty způsobem bezpečným pro typ. 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ů, které jsou stejného typu:
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 odkomentujte poslední řádek, nebude se kompilovat. I first test jsou typy delegátů, ale jsou to různé typy delegátů.
Omezení výčtu
Počínaje jazykem C# 7.3 můžete také zadat typ jako omezení System.Enum základní třídy. Modul CLR toto omezení vždycky povolené, ale jazyk C# ho nepovolil. Obecné typy, které používají , poskytují typové bezpečné programování pro ukládání výsledků ze statických metod v souboru do System.Enum System.Enum mezipaměti. Následující příklad vyhledá všechny platné hodnoty pro typ výčtu a pak vytvoří slovník, který mapuje tyto hodnoty na jeho ř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``Enum.GetNamea používat reflexi, což má vliv na výkon. Voláním metody můžete sestavit kolekci, která je uložena v mezipaměti a opakovaně použita, namísto opakování volání, EnumNamedValues 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}");