Visual Studio 2010 - Visual C# Constraints on Type Parameters (C# Programming Guide) When you define a generic class, you can apply restrictions to the kinds of types that client code can use for type arguments when it instantiates your class. If client code tries to instantiate your class by using a type that is not allowed by a constraint, the result is a compile-time error. These restrictions are called constraints. Constraints are specified by using the where contextual keyword. The following table lists the six types of constraints: Constraint | Description |
|---|
where T: struct | The type argument must be a value type. Any value type except Nullable can be specified. See Using Nullable Types (C# Programming Guide) for more information. | where T : class | The type argument must be a reference type; this applies also to any class, interface, delegate, or array type. | where T : new() | The type argument must have a public parameterless constructor. When used together with other constraints, the new() constraint must be specified last. | where T : <base class name> | The type argument must be or derive from the specified base class. | where T : <interface name> | The type argument must be or implement the specified interface. Multiple interface constraints can be specified. The constraining interface can also be generic. | where T : U | The type argument supplied for T must be or derive from the argument supplied for U. |

Why Use Constraints
If you want to examine an item in a generic list to determine whether it is valid or to compare it to some other item, the compiler must have some guarantee that the operator or method it has to call will be supported by any type argument that might be specified by client code. This guarantee is obtained by applying one or more constraints to your generic class definition. For example, the base class constraint tells the compiler that only objects of this type or derived from this type will be used as type arguments. Once the compiler has this guarantee, it can allow methods of that type to be called in the generic class. Constraints are applied by using the contextual keyword where. The following code example demonstrates the functionality we can add to the GenericList<T> class (in Introduction to Generics (C# Programming Guide)) by applying a base class constraint.
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;
}
}
The constraint enables the generic class to use the Employee.Name property because all items of type T are guaranteed to be either an Employee object or an object that inherits from Employee. Multiple constraints can be applied to the same type parameter, and the constraints themselves can be generic types, as follows:
class EmployeeList<T> where T : Employee, IEmployee, System.IComparable<T>, new()
{
// ...
}
By constraining the type parameter, you increase the number of allowable operations and method calls to those supported by the constraining type and all types in its inheritance hierarchy. Therefore, when you design generic classes or methods, if you will be performing any operation on the generic members beyond simple assignment or calling any methods not supported by System.Object, you will have to apply constraints to the type parameter. When applying the where T : class constraint, avoid the == and != operators on the type parameter because these operators will test for reference identity only, not for value equality. This is the case even if these operators are overloaded in a type that is used as an argument. The following code illustrates this point; the output is false even though the String class overloads the == operator.
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);
}
The reason for this behavior is that, at compile time, the compiler only knows that T is a reference type, and therefore must use the default operators that are valid for all reference types. If you must test for value equality, the recommended way is to also apply the where T : IComparable<T> constraint and implement that interface in any class that will be used to construct the generic class.

Constraining Multiple Parameters
You can apply constraints to multiple parameters, and multiple constraints to a single parameter, as shown in the following example:
class Base { }
class Test<T, U>
where U : struct
where T : Base, new() { }

Unbounded Type Parameters
Type parameters that have no constraints, such as T in public class SampleClass<T>{}, are called unbounded type parameters. Unbounded type parameters have the following rules: The != and == operators cannot be used because there is no guarantee that the concrete type argument will support these operators. They can be converted to and from System.Object or explicitly converted to any interface type. You can compare to null. If an unbounded parameter is compared to null, the comparison will always return false if the type argument is a value type.

Type Parameters as Constraints
The use of a generic type parameter as a constraint is useful when a member function with its own type parameter has to constrain that parameter to the type parameter of the containing type, as shown in the following example:
class List<T>
{
void Add<U>(List<U> items) where U : T {/*...*/}
}
In the previous example, T is a type constraint in the context of the Add method, and an unbounded type parameter in the context of the List class. Type parameters can also be used as constraints in generic class definitions. Note that the type parameter must be declared within the angle brackets together with any other type parameters:
//Type parameter V is used as a type constraint.
public class SampleClass<T, U, V> where T : V { }
The usefulness of type parameters as constraints with generic classes is very limited because the compiler can assume nothing about the type parameter except that it derives from System.Object. Use type parameters as constraints on generic classes in scenarios in which you want to enforce an inheritance relationship between two type parameters.

See Also
|
Visual Studio 2010 - Visual C# 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
|