Einschränkungen für Typparameter (C#-Programmierhandbuch)

Beim Definieren einer generischen Klasse können Sie Einschränkungen für diejenigen Typen anwenden, die der Clientcode für Typargumente verwenden kann, wenn er die Klasse instanziiert. Wenn der Clientcode versucht, die Klasse mit einem Typ zu instanziieren, der durch eine Beschränkung nicht zulässig ist, tritt ein Kompilierungsfehler auf. Diese Beschränkungen heißen Einschränkungen. Sie werden mit dem where-Kontextschlüsselwort angegeben. In der folgenden Tabelle werden die sechs Einschränkungstypen aufgelistet:

Einschränkung

Beschreibungen

where T: struct

Das Typargument muss ein Werttyp sein. Jeder Werttyp außer Nullable kann angegeben werden. Weitere Informationen finden Sie unter Verwenden von auf NULL festlegbaren Typen (C# Programmierhandbuch).

where T : class

Das Typargument muss ein Verweistyp sein. Dies gilt auch für Klassen-, Schnittstellen-, Delegat- und Arraytypen.

where T : new()

Das Typargument muss einen öffentlichen parameterlosen Konstruktor aufweisen. Wenn Sie die new()-Einschränkung mit anderen Einschränkungen verwenden, muss sie zuletzt angegeben werden.

where T : <Basisklassenname>

Das Typargument muss die Basisklasse sein oder daraus abgeleitet sein.

where T : <Schnittstellenname>

Das Typargument muss die angegebene Schnittstelle sein oder sie implementieren. Mehrere Schnittstelleneinschränkungen können angegeben werden. Die einschränkende Schnittstelle kann auch generisch sein.

where T : U

Das für T angegebene Typargument muss mit dem für U angegebenen Argument übereinstimmen oder davon abgeleitet werden.

Gründe für die Verwendung von Einschränkungen

Wenn Sie ein Element in einer generischen Liste untersuchen möchten, um seine Gültigkeit zu überprüfen oder um es mit einem anderen Element zu vergleichen, benötigt der Compiler eine Garantie, dass der aufzurufende Operator oder die aufzurufende Methode von den im Clientcode angegebenen Typargumenten unterstützt wird. Diese Garantie wird durch das Einfügen von Einschränkungen in die generische Klassendefinition erreicht. Die Basisklasseneinschränkung teilt dem Compiler mit, dass nur Objekte dieses Typs oder aus diesem Typ abgeleitete Objekte als Typargumente verwendet werden. Sobald der Compiler diese Garantie hat, kann er zulassen, dass Methoden dieses Typs innerhalb der generischen Klasse aufgerufen werden. Einschränkungen werden mit dem where-Kontextschlüsselwort angegeben. Im folgenden Codebeispiel werden die Funktionen dargestellt, die einer GenericList<T>-Klasse (in Einführung in Generika (C#-Programmierhandbuch)) durch Anwenden einer Basisklasseneinschränkung hinzugefügt werden können.

public class Employee
{
    private string name;
    private int id;

    public Employee(string s, int i)
    {
        name = s;
        id = i;
    }

    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    public int ID
    {
        get { return id; }
        set { id = value; }
    }
}

public class GenericList<T> where T : Employee
{
    private class Node
    {
        private Node next;
        private T data;

        public Node(T t)
        {
            next = null;
            data = t;
        }

        public Node Next
        {
            get { return next; }
            set { next = value; }
        }

        public T Data
        {
            get { return data; }
            set { data = value; }
        }
    }

    private Node head;

    public GenericList() //constructor
    {
        head = null;
    }

    public void AddHead(T t)
    {
        Node n = new Node(t);
        n.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;
    }
}

Die Einschränkung ermöglicht der generischen Klasse die Verwendung der Employee.Name-Eigenschaft, da alle Elemente des Typs T garantiert entweder Employee-Objekte sind oder Objekte, die von Employee erben.

Mehrere Einschränkungen können auf denselben Typparameter angewendet werden, und die Einschränkungen selbst können generische Typen sein:

class EmployeeList<T> where T : Employee, IEmployee, System.IComparable<T>, new()
{
    // ...
}

Durch das Einschränken des Typparameters erhöhen Sie die Anzahl der zulässigen Operationen und Methodenaufrufe um die Operationen und Methodenaufrufe, die vom Einschränkungstyp und allen Typen in seiner Vererbungshierarchie unterstützt werden. Wenn Sie Operationen mit generischen Membern durchführen, die über einfache Zuweisungen hinausgehen oder Methoden aufrufen, die nicht von System.Object unterstützt werden, sollten Sie daher beim Entwerfen von generischen Klassen und Methoden Einschränkungen auf die Typparameter anwenden.

Beim Anwenden der where T : class-Einschränkung wird empfohlen, die Operatoren == und != nicht für den Typparameter zu verwenden, da diese Operatoren nur die Referenzgleichheit und nicht die Wertgleichheit prüfen. Dies ist auch dann der Fall, wenn diese Operatoren in einem als Argument verwendeten Typ überladen werden. Im folgenden Code wird dies veranschaulicht: Die Ausgabe ist false, obwohl die String-Klasse den Operator == überlädt.

public static void OpTest<T>(T s, T t) where T : class
{
    System.Console.WriteLine(s == t);
}
static void Main()
{
    string s1 = "target";
    System.Text.StringBuilder sb = new System.Text.StringBuilder("target");
    string s2 = sb.ToString();
    OpTest<string>(s1, s2);
}

Der Grund für dieses Verhalten ist, dass der Compiler zur Kompilierzeit nur weiß, dass T ein Referenztyp ist. Deswegen muss er die Standardoperatoren verwenden, die für alle Referenztypen gültig sind. Falls Sie auf Wertgleichheit prüfen müssen, wird empfohlen, die where T : IComparable<T>-Einschränkung anzuwenden und diese Schnittstelle in jeder Klasse zu implementieren, mit der die generische Klasse erstellt wird.

Einschränken mehrerer Parameter

Wie im folgenden Beispiel gezeigt, können Sie eine Einschränkung auf mehrere Parameter, aber auch mehrere Einschränkungen auf einen einzelnen Parameter anwenden:

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

Ungebundene Typparameter

Typparameter ohne Einschränkungen, z. B. T in der öffentlichen SampleClass<T>{}-Klasse, werden als ungebundene Typparameter bezeichnet. Ungebundene Typparameter haben die folgenden Regeln:

  • Die Operatoren != und == können nicht verwendet werden, weil es keine Garantie gibt, dass das konkrete Typargument diese Operatoren unterstützt.

  • Sie können in und von System.Object konvertiert werden oder explizit in einen Schnittstellentyp konvertiert werden.

  • Vergleiche mit null sind möglich. Falls ein ungebundener Parameter mit null verglichen wird, wird der Vergleich immer false zurückgeben, wenn das Argument ein Werttyp ist.

Typparameter als Einschränkungen

Die Verwendung eines generischen Typparameters als Einschränkung ist nützlich, wenn eine Memberfunktion mit eigenem Typparameter diesen auf den Typparameter des enthaltenden Typs einschränken muss. Dies wird im folgenden Beispiel veranschaulicht:

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

Im vorherigen Beispiel ist T im Kontext der Add-Methode eine Typeinschränkung und im Kontext der List-Klasse ein ungebundener Typparameter.

Typparameter können auch als Einschränkungen in generischen Klassendefinitionen verwendet werden. Beachten Sie, dass der Typparameter in spitzen Klammern zusammen mit allen anderen Typparameter deklariert werden muss:

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

Der Nutzen von Typparametern als Einschränkungen ist bei generischen Klassen sehr begrenzt, weil der Compiler keine Informationen über den Typparameter hat, außer, dass er von System.Object abgeleitet ist. Verwenden Sie Typparameter als Einschränkungen für generische Klassen in Szenarios, in denen Sie eine Vererbungsbeziehung zwischen zwei Typparametern erzwingen möchten.

Siehe auch

Referenz

Einführung in Generika (C#-Programmierhandbuch)

Generische Klassen (C#-Programmierhandbuch)

new-Einschränkung (C#-Referenz)

System.Collections.Generic

Konzepte

C#-Programmierhandbuch