Típusparaméterekre vonatkozó korlátozások (C# programozási útmutató)
A korlátozások tájékoztatják a fordítót arról, hogy milyen képességekkel kell rendelkeznie egy típusargumentumnak. Korlátozások nélkül a típusargumentum bármilyen típus lehet. A fordító csak a .NET-típus végső alaposztályának System.Objecttagjait feltételezheti. További információ: Miért használjon korlátozásokat. Ha az ügyfélkód olyan típust használ, amely nem felel meg a korlátozásnak, a fordító hibát ad ki. A kényszereket a where
környezetfüggő kulcsszóval adhatók meg. Az alábbi táblázat a kényszerek különböző típusait sorolja fel:
Megszorítás | Leírás |
---|---|
where T : struct |
A típusargumentumnak nem null értékű értéknek kell lennie, amely típusokat is tartalmaz record struct . A null értékű értéktípusokról további információt a Null értékű értéktípusok című témakörben talál. Mivel minden értéktípushoz elérhető paraméter nélküli konstruktor van deklarálva vagy implicit módon, a struct kényszer a new() kényszert jelenti, és nem kombinálható a new() kényszerrel. A kényszer nem kombinálható a struct unmanaged kényszerrel. |
where T : class |
A típusargumentumnak referenciatípusnak kell lennie. Ez a korlátozás minden osztályra, felületre, delegálásra vagy tömbtípusra is érvényes. Null értékű környezetben T nem null értékű hivatkozástípusnak kell lennie. |
where T : class? |
A típusargumentumnak null értékű vagy nem null értékű hivatkozástípusnak kell lennie. Ez a korlátozás minden osztályra, felületre, delegálásra vagy tömbtípusra is vonatkozik, beleértve a rekordokat is. |
where T : notnull |
A típusargumentumnak nem null értékű típusnak kell lennie. Az argumentum lehet nem null értékű hivatkozástípus vagy nem null értékű értéktípus. |
where T : unmanaged |
A típusargumentumnak nem null értékű , nem felügyelt típusnak kell lennie. A unmanaged kényszer a kényszert struct jelenti, és nem kombinálható sem a kényszerekkel, sem a struct new() korlátozásokkal. |
where T : new() |
A típusargumentumnak nyilvános paraméter nélküli konstruktorsal kell rendelkeznie. Ha más korlátozásokkal együtt használják, a kényszert utoljára new() kell megadni. A new() kényszer nem kombinálható a korlátozásokkal és unmanaged a struct korlátozásokkal. |
where T : <alaposztály neve> |
A típusargumentumnak a megadott alaposztályból kell lennie vagy származnia. Null értékű környezetben a megadott alaposztályból származó nem T null értékű hivatkozástípusnak kell lennie. |
where T : <alaposztály neve>? |
A típusargumentumnak a megadott alaposztályból kell lennie vagy származnia. Null értékű környezetben T lehet null értékű vagy nem null értékű típus, amely a megadott alaposztályból származik. |
where T : <interfész neve> |
A típusargumentumnak a megadott illesztőnek kell lennie vagy implementálnia. Több felületi korlátozás is megadható. A korlátozó felület általános is lehet. Null értékű környezetben a megadott felületet megvalósító nem T null értékű típusnak kell lennie. |
where T : <felület neve>? |
A típusargumentumnak a megadott illesztőnek kell lennie vagy implementálnia. Több felületi korlátozás is megadható. A korlátozó felület általános is lehet. Null értékű környezetben T lehet null értékű hivatkozástípus, nem null értékű hivatkozástípus vagy értéktípus. T nem lehet null értékű típus. |
where T : U |
A megadott típusargumentumnak T a megadott U argumentumból kell vagy származnia. Null értékű környezetben, ha U nem null értékű hivatkozástípus, T akkor nem null értékű hivatkozástípusnak kell lennie. Ha U null értékű hivatkozástípus, T akkor lehet null értékű vagy nem null értékű. |
where T : default |
Ez a korlátozás feloldja a kétértelműséget, ha nem engedélyezett típusparamétert kell megadnia egy metódus felülbírálásakor vagy explicit felületi implementáció biztosításakor. A default kényszer azt jelenti, hogy az alapmetódus vagy a class struct kényszer nélkül is elérhető. További információkért tekintse meg a default kényszer-specifikáció javaslatát. |
Egyes kényszerek kölcsönösen kizárják egymást, és egyes korlátozásoknak meghatározott sorrendben kell lenniük:
- Legfeljebb az
struct
egyik ,class
, ,class?
notnull
ésunmanaged
korlátozás alkalmazható. Ha ezen kényszerek bármelyikét megadja, annak kell lennie az adott típusparaméterhez megadott első kényszernek. - Az alaposztály kényszere (
where T : Base
vagywhere T : Base?
) nem kombinálható egyetlen korlátozássalstruct
sem ,class
semnotnull
class?
unmanaged
. - Legfeljebb egy alaposztály-korlátozást alkalmazhat mindkét formában. Ha támogatni szeretné a null értékű alaptípust, használja a következőt
Base?
: . - Az illesztő nem null értékű és null értékű formáját sem nevezheti kényszernek.
- A
new()
kényszer nem kombinálható a korlátozással vagyunmanaged
astruct
korlátozással. Ha megadja anew()
kényszert, annak kell lennie az adott típusparaméter utolsó kényszerének. - A
default
korlátozás csak felülbírálási vagy explicit felületi implementációkra alkalmazható. Nem kombinálható sem a korlátozásokkal, sem astruct
class
korlátozásokkal.
Miért érdemes kényszereket használni?
A korlátozások megadják egy típusparaméter képességeit és elvárásait. A korlátozások deklarálása azt jelenti, hogy használhatja a korlátozástípus műveleteit és metódushívásait. A típusparaméterre korlátozásokat alkalmazhat, ha az általános osztály vagy metódus az egyszerű hozzárendelésen túl bármilyen műveletet használ az általános tagokon, beleértve a nem támogatott metódusok meghívását System.Objectis. Az alaposztály-korlátozás például azt jelzi a fordítónak, hogy csak az ilyen típusú vagy ebből a típusból származtatott objektumok helyettesíthetik ezt a típusargumentumot. Ha a fordító rendelkezik ezzel a garanciával, lehetővé teszi az ilyen típusú metódusok meghívását az általános osztályban. Az alábbi példakód bemutatja az osztályhoz hozzáadható funkciókat (a Bevezetés a GenericList<T>
Genericsba) egy alaposztály-korlátozás alkalmazásával.
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;
}
}
A korlátozás lehetővé teszi, hogy az általános osztály használja a tulajdonságot Employee.Name
. A korlátozás azt határozza meg, hogy az összes elemtípus T
garantáltan objektum Employee
vagy egy olyan objektum legyen, amelytől Employee
öröklődik.
Ugyanarra a típusparaméterre több kényszer is alkalmazható, és maguk a kényszerek lehetnek általános típusok, az alábbiak szerint:
class EmployeeList<T> where T : Employee, System.Collections.Generic.IList<T>, IDisposable, new()
{
// ...
}
A kényszer alkalmazásakor where T : class
kerülje a típusparaméter és !=
operátorok ==
használatát, mert ezek az operátorok csak a referencia-identitást tesztelik, nem az értékegyenlőséget. Ez a viselkedés akkor is előfordul, ha ezek az operátorok túlterheltek egy argumentumként használt típusban. A következő kód ezt a pontot szemlélteti; a kimenet hamis, annak ellenére, hogy az String osztály túlterheli az operátort ==
.
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);
}
A fordító csak azt tudja, hogy T
fordításkor ez egy referenciatípus, és az összes referenciatípusra érvényes alapértelmezett operátorokat kell használnia. Ha tesztelnie kell az értékegyenlőséget, alkalmazza a where T : IEquatable<T>
korlátozást, where T : IComparable<T>
és implementálja a felületet az általános osztály létrehozásához használt bármely osztályban.
Több paraméter korlátozása
A kényszereket több paraméterre, egy paraméterre pedig több kényszerre is alkalmazhatja, ahogyan az alábbi példában látható:
class Base { }
class Test<T, U>
where U : struct
where T : Base, new()
{ }
Kötetlen típusparaméterek
Az olyan típusparamétereket, amelyek nem rendelkeznek korlátozásokkal, például A nyilvános osztályban SampleClass<T>{}
, kötetlen típusparamétereknek nevezzük. A kötetlen típusparaméterek a következő szabályokkal rendelkeznek:
- Az
!=
és==
operátorok nem használhatók, mert nincs garancia arra, hogy a konkrét típusú argumentum támogatja ezeket az operátorokat. - Ezek konvertálhatók bármely felülettípusra, amelyből származnak
System.Object
, vagy explicit módon átalakíthatók. - A null értékhez hasonlíthatja őket. Ha egy kötetlen paraméterrel van összehasonlítva
null
, az összehasonlítás mindig hamis értéket ad vissza, ha a típusargumentum értéktípus.
Paraméterek beírása kényszerként
Az általános típusparaméter kényszerként való használata akkor hasznos, ha egy saját típusparaméterrel rendelkező tagfüggvénynek ezt a paramétert a tartalmazó típus típusparaméteréhez kell korlátoznia, ahogyan az az alábbi példában látható:
public class List<T>
{
public void Add<U>(List<U> items) where U : T {/*...*/}
}
Az előző példában T
a metódus kontextusában Add
egy típuskényszer, az osztály környezetében List
pedig egy kötetlen típusparaméter látható.
A típusparaméterek az általános osztálydefiníciók kényszereiként is használhatók. A típusparamétert a szögletes zárójeleken belül kell deklarálni bármely más típusparaméterrel együtt:
//Type parameter V is used as a type constraint.
public class SampleClass<T, U, V> where T : V { }
A típusparaméterek általános osztályokra vonatkozó megkötésekként való hasznossága korlátozott, mert a fordító semmit sem tud feltételezni a típusparaméterről, kivéve, hogy az származik System.Object
belőle. Típusparaméterek használata az általános osztályokra vonatkozó korlátozásokként olyan helyzetekben, amelyekben két típusparaméter közötti öröklési kapcsolatot szeretne kikényszeríteni.
notnull
kényszer
A kényszer használatával notnull
megadhatja, hogy a típusargumentumnak nem null értékű vagy nem null értékű hivatkozástípusnak kell lennie. A legtöbb más kényszertől eltérően, ha egy típusargumentum megsérti a notnull
kényszert, a fordító hibaüzenet helyett figyelmeztetést hoz létre.
A notnull
kényszer csak null értékű környezetben való használat esetén érvényes. Ha a notnull
kényszert null értékű, oblivious környezetben adja hozzá, a fordító nem generál figyelmeztetést vagy hibát a kényszer megsértése esetén.
class
kényszer
A class
null értékű környezet kényszere azt határozza meg, hogy a típusargumentumnak nem null értékű hivatkozástípusnak kell lennie. Null értékű környezetben, ha egy típusargumentum null értékű hivatkozástípus, a fordító figyelmeztetést hoz létre.
default
kényszer
A null értékű hivatkozástípusok hozzáadása bonyolítja az általános típus vagy metódus használatát T?
. T?
használható a korlátozással vagy class
a struct
korlátozással, de az egyiknek jelen kell lennie. A class
kényszer használatakor T?
a null értékű hivatkozástípusra hivatkozik a következőhöz T
: . T?
akkor használható, ha egyik korlátozást sem alkalmazza. Ebben az esetben T?
a rendszer az értéktípusok és a referenciatípusok esetében értelmezi T?
. Ha T
azonban egy példány Nullable<T>, akkor ugyanaz, T?
mint T
a . Más szóval, ez nem válik T??
.
Mivel T?
most már a kényszer vagy struct
a class
korlátozás nélkül is használható, kétértelműségek merülhetnek fel a felülbírálásokban vagy explicit felületi implementációkban. Mindkét esetben a felülbírálás nem tartalmazza a korlátozásokat, hanem az alaposztálytól örökli őket. Ha az alaposztály nem alkalmazza sem a class
struct
kényszert, a származtatott osztályoknak valamilyen módon felül kell írniuk az alapmetódusra vonatkozó felülbírálást kényszer nélkül. A származtatott módszer alkalmazza a kényszert default
. A default
kényszer nem tisztázza sem a kényszert, sem struct
a korlátozástclass
.
Nem felügyelt kényszer
A kényszer használatával unmanaged
megadhatja, hogy a típusparaméternek nem null értékű , nem felügyelt típusnak kell lennie. A unmanaged
korlátozás lehetővé teszi, hogy újrahasználható rutinokat írjon a memóriablokkokként kezelhető típusokkal való munkához, ahogyan az az alábbi példában látható:
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;
}
Az előző metódust egy unsafe
környezetben kell lefordítani, mert az sizeof
operátort olyan típuson használja, amely nem ismert, hogy beépített típus. A unmanaged
korlátozás nélkül az sizeof
operátor nem érhető el.
A unmanaged
kényszer a kényszert struct
jelenti, és nem kombinálható vele. Mivel a struct
kényszer a new()
kényszert jelenti, a unmanaged
kényszer nem kombinálható a new()
kényszerrel is.
Delegálási korlátozások
Használhatja System.Delegate vagy System.MulticastDelegate alaposztály-korlátozásként is. A CLR mindig engedélyezte ezt a korlátozást, de a C# nyelv nem engedélyezte. A System.Delegate
korlátozás lehetővé teszi olyan kód írását, amely típusbiztos módon működik a meghatalmazottakkal. A következő kód egy olyan bővítménymetódust határoz meg, amely két meghatalmazottat egyesít, feltéve, hogy azonos típusúak:
public static TDelegate? TypeSafeCombine<TDelegate>(this TDelegate source, TDelegate target)
where TDelegate : System.Delegate
=> Delegate.Combine(source, target) as TDelegate;
A fenti módszerrel azonos típusú meghatalmazottakat egyesíthet:
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);
Ha visszavonja az utolsó sort, az nem lesz lefordítva. Mindkettő first
delegálási test
típus, de eltérő delegálási típus.
Enumerálási korlátozások
A típust System.Enum alaposztály-korlátozásként is megadhatja. A CLR mindig engedélyezte ezt a korlátozást, de a C# nyelv nem engedélyezte. Az általános generikusok System.Enum
típusbiztos programozást biztosítanak a statikus metódusok által kapott eredmények gyorsítótárazásához a következőben System.Enum
: . Az alábbi minta megkeresi az enum típus összes érvényes értékét, majd létrehoz egy szótárt, amely ezeket az értékeket a sztring-ábrázoláshoz rendeli.
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
és Enum.GetName
használjon tükröződést, amely teljesítménybeli következményekkel jár. Meghívhat EnumNamedValues
egy gyorsítótárazott és újrafelhasznált gyűjteményt ahelyett, hogy megismételte a tükrözést igénylő hívásokat.
Az alábbi példában látható módon létrehozhat egy enumerát, és létrehozhat egy szótárt az értékeiből és a neveiből:
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}");
A típusargumentumok deklarált felületet implementálnak
Egyes forgatókönyvek megkövetelik, hogy egy típusparaméterhez megadott argumentum implementálja ezt az interfészt. Példa:
public interface IAdditionSubtraction<T> where T : IAdditionSubtraction<T>
{
static abstract T operator +(T left, T right);
static abstract T operator -(T left, T right);
}
Ez a minta lehetővé teszi a C#-fordító számára, hogy meghatározza a túlterhelt operátorok vagy metódusok static virtual
static abstract
tartalmazó típusát. Megadja a szintaxist, hogy az összeadási és kivonási operátorok definiálhatók legyenek egy tartalmazó típuson. E kényszer nélkül a paramétereket és argumentumokat interfészként kell deklarálni a típusparaméter helyett:
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);
}
Az előző szintaxis megköveteli, hogy a implementátorok explicit felületi implementációt használjanak ezekhez a módszerekhez. Az extra korlátozás megadása lehetővé teszi, hogy az interfész a típusparaméterek szempontjából határozza meg az operátorokat. Az interfészt megvalósító típusok implicit módon implementálhatják a felületi metódusokat.
Lásd még
Visszajelzés
https://aka.ms/ContentUserFeedback.
Hamarosan elérhető: 2024-ben fokozatosan kivezetjük a GitHub-problémákat a tartalom visszajelzési mechanizmusaként, és lecseréljük egy új visszajelzési rendszerre. További információ:Visszajelzés küldése és megtekintése a következőhöz: