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

Aktualisiert: Juli 2008

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

Beschreibung

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 Typargument, das für T angegeben wird, muss entweder das für U angegebene Argument sein oder davon abgeleitet sein. Dies wird als naked-Typeinschränkung bezeichnet.

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 = "foo";
    System.Text.StringBuilder sb = new System.Text.StringBuilder("foo");
    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 nicht gebundene Typparameter bezeichnet. Nicht gebundene 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 nicht gebundener Parameter mit null verglichen wird, wird der Vergleich immer false zurückgeben, wenn das Argument ein Werttyp ist.

naked-Typeinschränkungen

Wenn ein generischer Typparameter als Einschränkung verwendet wird, wird dies als naked-Typeinschränkung bezeichnet. naked-Typeinschränkungen sind 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 naked-Typeinschränkung und im Kontext der List-Klasse ein nicht gebundener Typparameter.

naked-Typeinschränkungen können auch in generischen Klassendefinitionen verwendet werden. Beachten Sie, dass auch die naked-Typeinschränkung innerhalb der Winkelklammern zusammen mit anderen Typparametern deklariert werden muss:

//naked type constraint
public class SampleClass<T, U, V> where T : V { }

Der Nutzen von naked-Typeinschränkungen ist sehr begrenzt, weil der Compiler keine Informationen zu einer naked-Typeinschränkung hat, außer, dass sie von System.Object abgeleitet ist. Verwenden Sie naked-Typeinschränkungen für generische Klassen in Szenarios, in denen Sie eine Vererbungsbeziehung zwischen zwei Typparametern erzwingen möchten.

Siehe auch

Konzepte

C#-Programmierhandbuch

Referenz

Einführung in Generika (C#-Programmierhandbuch)

new-Einschränkung (C#-Referenz)

System.Collections.Generic

Änderungsprotokoll

Datum

Versionsgeschichte

Grund

Juli 2008

Absatz zu mehreren Einschränkungen hinzugefügt.

Korrektur inhaltlicher Fehler.