Megosztás a következőn keresztül:


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 Uargumentumbó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 structegyik , class, , class?notnullés unmanaged 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 vagy where T : Base?) nem kombinálható egyetlen korlátozással structsem , classsem notnullclass?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 vagy unmanaged a struct korlátozással. Ha megadja a new() 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 a struct 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.Objectbelő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 Ta . 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