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

Wenn Sie eine generische Klasse definieren, können Sie Beschränkungen auf die Arten der Typen anwenden, die Clientcode für generische Typargumente verwenden kann, wenn er Ihre Klasse instanziiert. Wenn Clientcode versucht, Ihre Klasse zu instanziieren, indem er einen Typ verwendet, der durch Ihre Einschränkung nicht erlaubt ist, kommt es zu einem Kompilierzeitfehler. Diese Einschränkungen werden als Constraints bezeichnet. Constraints werden mit dem kontextuellen Schlüsselwort where angegeben. In der folgenden Tabelle werden die sechs verschiedenen Contrainttypen aufgelistet:

Constraint Beschreibung
where T: struct Das Typargument muss ein Werttyp sein. Jeder Werttyp außer <xref:System.Nullable> kann angegeben werden. Weitere Informationen finden Sie unter Using Nullable Types (Verwenden von Nullable-Typen).
where T : class Das Typargument muss ein Verweistyp sein. Dies gilt ebenfalls für Klassen-, Schnittstellen-, Delegat- oder Arraytypen.
where T : new() Das Typargument muss einen öffentlichen, parameterlosen Konstruktor aufweisen. Beim gemeinsamen Verwenden anderen Constraints muss der new()-Constraint zuletzt angegeben werden.
where T : <basisklassenname> Das Typargument muss die angegebene Basisklasse sein oder von dieser abgeleitet werden.
where T : <schnittstellenname> Das Typargument muss die angegebene Schnittstelle sein oder diese implementieren. Es können mehrere Schnittstelleneinschränkungen angegeben werden. Die einschränkende Schnittstelle kann auch generisch sein.
where T : U Das Typargument, das für T angegeben wurde, muss das für T angegebene Argument sein oder von diesem abgeleitet werden.

Weshalb Constraints?

Wenn Sie untersuchen möchten, ob ein bestimmtes Argument in einer generischen Liste gültig ist oder es mit einem anderen Element vergleichen möchten, muss der Compiler eine Garantie haben, dass der Operator oder die Methode, die er aufgerufen hat, von jedem Typargument unterstützt wird, das von Clientcode angegeben werden kann. Diese Garantie wird gegeben, indem Sie einen oder mehrere Constraints auf Ihre generische Klassendefinition anwenden. Der Basisklassenconstraint sagt dem Compiler z.B., dass nur Objekte dieses Typs oder Objekte, die von diesem Typ abgeleitet werden, als Typargumente verwendet werden. Sobald der Compiler diese Garantie hat, kann er erlauben, dass Methoden dieses Typs in der generischen Klasse aufgerufen werden können. Constraints werden mit dem kontextuellen Schlüsselwort where angewendet. Im folgenden Codebeispiel wird die Funktionalität veranschaulicht, die der GenericList<T>-Klasse durch das Anwenden eines Basisklassenconstraints hinzugefügt werden kann (in Einführung in Generika).

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;
    }
}

Mit dem Constraint kann die generische Klasse die Employee.Name-Eigenschaft verwenden, da alle Elemente des Typs T sicher ein Employee-Objekt oder ein Objekt, das von Employee erbt, sind.

Mehrere Constraints können wie folgt auf den gleichen Typenparameter angewendet werden, und die Contraints können selbst generische Typen sein:

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

Indem Sie den Typparameter einschränken, erhöhen Sie die Zahl an zulässigen Vorgängen und Methodenaufrufen von denjenigen, die vom einschränkenden Typ und allen Typen in dessen Vererbungshierarchie unterstützt werden. Beim Entwerfen generischer Klassen und Methoden müssen Sie Constraints auf den Typparameter anwenden, wenn Sie Vorgänge mit den generischen Membern durchführen möchten, die über das einfache Zuweisen und Aufrufen von Methoden hinausgehen, die nicht von System.Object unterstützt werden.

Wenn Sie den Constraint where T : class anwenden, vermeiden Sie das Verwenden der Operatoren == und != mit dem Typparameter, da diese nur auf Verweisidentität und nicht auf Wertgleichheit prüfen. Dies ist auch der Fall, wenn diese Operatoren in einem Typ überladen werden, der als Argument verwendet wird. Der folgende Code veranschaulicht diesen Aspekt. Die Ausgabe ist FALSE, obwohl die <xref:System.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);
}

Dieses Verhalten tritt auf, da der Compiler zur Kompilierzeit nur weiß, dass T ein Verweistyp ist, und deshalb die Standardoperatoren verwenden muss, die für alle Verweistypen zulässig sind. Wenn Sie auf Wertgleichheit prüfen müssen, wird empfohlen, dass Sie den where T : IComparable<T>-Constraint anwenden und die Schnittstelle in jeder Klasse implementieren, die verwendet wird, um die generische Klasse zu erstellen.

Einschränken mehrerer Parameter

Sie können wie im folgenden Beispiel gezeigt Constraints auf mehrere Parameter und mehrere Constraints auf einen einzelnen Parameter anwenden:

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

Ungebundene Typparameter

Typparameter, auf die keine Constraints angewendet wurden, wie z.B. T in der öffentlichen Klasse SampleClass<T>{}, werden als ungebundene Typparameter bezeichnet. Für ungebundene Typparameter gelten die folgenden Regeln:

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

  • Sie können in und aus System.Object oder implizit in einen Schnittstellentyp konvertiert werden.

  • Sie können mit NULL vergleichen. Wenn ein ungebundener Parameter mit null verglichen wird, gibt der Vergleich immer FALSE zurück, wenn das Typargument ein Werttyp ist.

Typparameter als Constraints

Es ist nützlich, einen Typparameter wie in folgendem Beispiel gezeigt als Constraint zu verwenden, wenn eine Memberfunktion mit ihren eigenen Typparametern diesen Parameter auf den Typparameter des enthaltenden Typs einschränken muss:

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

Im vorherigen Beispiel ist T ein Typconstraint im Kontext der Add-Methode und ein ungebundener Typparameter im Kontext der List-Klasse.

Typparameter können auch in generischen Klassendefinitionen als Constraints verwendet werden. Beachten Sie, dass Typparameter in spitzen Klammern zusammen mit allen anderen Typparametern deklariert werden müssen.

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

Das Verwenden von Typparametern als Constraints für generische Klassen ist nur bis zu einem gewissen Punkt nützlich, da der Compiler keine Informationen über den Typparameter annehmen kann, nur dass er von System.Object abgeleitet ist. Sie sollten Typparameter als Constraints dann verwenden, wenn Sie eine Vererbungsbeziehung zwischen zwei Typparametern erzwingen möchten.

Siehe auch

<xref:System.Collections.Generic>
C#-Programmierhandbuch
Einführung in Generika
Generische Klassen
new-Einschränkung