Einschränkungen für Typparameter (C#-Programmierhandbuch)Constraints on type parameters (C# Programming Guide)

Einschränkungen informieren den Compiler über die Funktionen, über die ein Typargument verfügen muss.Constraints inform the compiler about the capabilities a type argument must have. Ohne Einschränkungen könnte das Typargument jedes beliebige Argument sein.Without any constraints, the type argument could be any type. Der Compiler kann nur die Object-Elemente annehmen. Dies ist die übergeordnete Basisklasse für jeden beliebigen .NET-Typ.The compiler can only assume the members of Object, which is the ultimate base class for any .NET type. Weitere Informationen finden Sie unter Weshalb Einschränkungen?.For more information, see Why use constraints. 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.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. Constraints werden mit dem kontextuellen Schlüsselwort where angegeben.Constraints are specified by using the where contextual keyword. In der folgenden Tabelle werden die sieben verschiedenen Einschränkungstypen aufgelistet:The following table lists the seven types of constraints:

ConstraintConstraint Beschreibung Description
where T : struct Das Typargument muss ein Werttyp sein.The type argument must be a value type. Jeder Werttyp außer Nullable<T> kann angegeben werden.Any value type except Nullable<T> can be specified. Weitere Informationen zu Typen, die NULL-Werte zulassen, finden Sie unter Nullable-Typen (C#-Programmierhandbuch).For more information about nullable types, see Nullable types.
where T : class Das Typargument muss ein Verweistyp sein.The type argument must be a reference type. Diese Einschränkung gilt auch für jede Klasse, Schnittstelle, jeden Delegaten oder Arraytyp.This constraint applies also to any class, interface, delegate, or array type.
where T : unmanaged Das Typargument darf kein Verweistyp sein und darf keine Verweistypmitglieder auf einer Schachtelungsebene enthalten.The type argument must not be a reference type and must not contain any reference type members at any level of nesting.
where T : new() Das Typargument muss einen öffentlichen, parameterlosen Konstruktor aufweisen.The type argument must have a public parameterless constructor. Beim gemeinsamen Verwenden anderen Constraints muss der new()-Constraint zuletzt angegeben werden.When used together with other constraints, the new() constraint must be specified last.
where T : <Basisklassenname>where T : <base class name> Das Typargument muss die angegebene Basisklasse sein oder von dieser abgeleitet werden.The type argument must be or derive from the specified base class.
where T : <Schnittstellenname>where T : <interface name> Das Typargument muss die angegebene Schnittstelle sein oder diese implementieren.The type argument must be or implement the specified interface. Es können mehrere Schnittstelleneinschränkungen angegeben werden.Multiple interface constraints can be specified. Die einschränkende Schnittstelle kann auch generisch sein.The constraining interface can also be generic.
where T : U Das Typargument, das für T angegeben wurde, muss das für T angegebene Argument sein oder von diesem abgeleitet werden.The type argument supplied for T must be or derive from the argument supplied for U.

Einige der Einschränkungen schließen einander aus.Some of the constraints are mutually exclusive. Alle Werttypen müssen einen zugänglichen, parameterlosen Konstruktor haben.All value types must have an accessible parameterless constructor. Die struct-Einschränkung impliziert die new()-Einschränkung, und die new()-Einschränkung kann nicht mit der struct-Einschränkung kombiniert werden.The struct constraint implies the new() constraint and the new() constraint cannot be combined with the struct constraint. Die unmanaged-Einschränkung impliziert die struct-Einschränkung.The unmanaged constraint implies the struct constraint. Die unmanaged-Einschränkung kann nicht mit der struct- oder new()-Einschränkung kombiniert werden.The unmanaged constraint cannot be combined with either the struct or new() constraints.

Weshalb Einschränkungen?Why use constraints

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.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. Beim Entwerfen generischer Klassen und Methoden müssen Sie Einschränkungen 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.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. Der Basisklassenconstraint sagt dem Compiler z.B., dass nur Objekte dieses Typs oder Objekte, die von diesem Typ abgeleitet werden, als Typargumente verwendet werden.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. Sobald der Compiler diese Garantie hat, kann er erlauben, dass Methoden dieses Typs in der generischen Klasse aufgerufen werden können.Once the compiler has this guarantee, it can allow methods of that type to be called in the generic class. Im folgenden Codebeispiel wird die Funktionalität veranschaulicht, die der GenericList<T>-Klasse durch das Anwenden einer Basisklasseneinschränkung hinzugefügt werden kann (in Einführung in Generika).The following code example demonstrates the functionality you can add to the GenericList<T> class (in Introduction to Generics) by applying a base class constraint.

public class Employee
{
    public Employee(string s, int i) => (Name, ID) = (s, i);
    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;
    }
}

Die Einschränkung ermöglicht der generischen Klasse, die Employee.Name-Eigenschaft zu verwenden.The constraint enables the generic class to use the Employee.Name property. Die Einschränkung gibt an, dass alle Elemente des Typs T entweder ein Employee-Objekt oder ein Objekt sind, das von Employee erbt.The constraint specifies that all items of type T are guaranteed to be either an Employee object or an object that inherits from Employee.

Mehrere Constraints können wie folgt auf den gleichen Typenparameter angewendet werden, und die Contraints können selbst generische Typen sein: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()
{
    // ...
}

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.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. Dieses Verhalten tritt auch auf, wenn diese Operatoren in einem Typ überladen werden, der als Argument verwendet wird.This behavior occurs even if these operators are overloaded in a type that is used as an argument. Der folgende Code veranschaulicht diesen Aspekt. Die Ausgabe ist FALSE, obwohl die String-Klasse den ==-Operator überlädt.The following code illustrates this point; the output is false even though the String class overloads the == operator.

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

Der Compiler weiß zur Kompilierzeit nur, dass T ein Verweistyp ist, und er deshalb die Standardoperatoren verwenden muss, die für alle Verweistypen zulässig sind.The compiler only knows that T is a reference type at compile time and must use the default operators that are valid for all reference types. Wenn Sie auf Wertgleichheit prüfen müssen, wird empfohlen, dass Sie die where T : IEquatable<T>- oder where T : IComparable<T>-Einschränkung anwenden und die Schnittstelle in jeder Klasse implementieren, die verwendet wird, um die generische Klasse zu erstellen.If you must test for value equality, the recommended way is to also apply the where T : IEquatable<T> or where T : IComparable<T> constraint and implement the interface in any class that will be used to construct the generic class.

Einschränken mehrerer ParameterConstraining multiple parameters

Sie können wie im folgenden Beispiel gezeigt Constraints auf mehrere Parameter und mehrere Constraints auf einen einzelnen Parameter anwenden: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()
{ }

Ungebundene TypparameterUnbounded type parameters

Typparameter, auf die keine Constraints angewendet wurden, wie z.B. T in der öffentlichen Klasse SampleClass<T>{}, werden als ungebundene Typparameter bezeichnet.Type parameters that have no constraints, such as T in public class SampleClass<T>{}, are called unbounded type parameters. Für ungebundene Typparameter gelten die folgenden Regeln:Unbounded type parameters have the following rules:

  • Die Operatoren != und == können nicht verwendet werden, weil es keine Garantie gibt, dass das jeweilige Typargument diese auch unterstützt.The != and == operators cannot be used because there is no guarantee that the concrete type argument will support these operators.
  • Sie können in und aus System.Object oder implizit in einen Schnittstellentyp konvertiert werden.They can be converted to and from System.Object or explicitly converted to any interface type.
  • Sie können sie mit NULL vergleichen.You can compare them to null. Wenn ein ungebundener Parameter mit null verglichen wird, gibt der Vergleich immer FALSE zurück, wenn das Typargument ein Werttyp ist.If an unbounded parameter is compared to null, the comparison will always return false if the type argument is a value type.

Typparameter als EinschränkungenType parameters as 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: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:

public class List<T>
{
    public 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.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.

Typparameter können auch in generischen Klassendefinitionen als Constraints verwendet werden.Type parameters can also be used as constraints in generic class definitions. Der Typparameter in spitzen Klammern muss zusammen mit allen anderen Typparametern deklariert werden: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 { }

Das Verwenden von Typparametern als Einschränkungen 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.The usefulness of type parameters as constraints with generic classes is limited because the compiler can assume nothing about the type parameter except that it derives from System.Object. Sie sollten Typparameter als Constraints dann verwenden, wenn Sie eine Vererbungsbeziehung zwischen zwei Typparametern erzwingen möchten.Use type parameters as constraints on generic classes in scenarios in which you want to enforce an inheritance relationship between two type parameters.

Nicht verwaltete EinschränkungenUnmanaged constraint

Ab C# 7.3 können Sie die unmanaged-Einschränkung nutzen, um anzugeben, dass der Typparameter ein nicht verwalteter Typ sein muss.Beginning with C# 7.3, you can use the unmanaged constraint to specify that the type parameter must be an unmanaged type. Ein nicht verwalteter Typ ist ein Typ, der kein Verweistyp ist, und keine Verweistypfelder auf Schachtelungsebene aufweist.An unmanaged type is a type that is not a reference type and doesn't contain reference type fields at any level of nesting. Die unmanaged-Einschränkung ermöglicht Ihnen das Schreiben von wiederverwendbarer Routinen zum Arbeiten mit Typen, die als Speicherblöcke bearbeitet werden können, wie im folgenden Beispiel gezeigt:The unmanaged constraint enables you to write reusable routines to work with types that can be manipulated as blocks of memory, as shown in the following example:

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

Die vorherige Methode muss in einen unsafe-Kontext kompiliert werden, da sie den sizeof-Operator für einen nicht bekannten Typ verwendet, der ein integrierter Typ ist.The preceding method must be compiled in an unsafe context because it uses the sizeof operator on a type not known to be a built-in type. Ohne die unmanaged-Einschränkung ist der sizeof-Operator nicht verfügbar.Without the unmanaged constraint, the sizeof operator is unavailable.

Delegieren von EinschränkungenDelegate constraints

Ab C# 7.3 können Sie auch System.Delegate oder System.MulticastDelegate als Basisklasseneinschränkung verwenden.Also beginning with C# 7.3, you can use System.Delegate or System.MulticastDelegate as a base class constraint. Die CLR lässt diese Einschränkung immer zu, aber die C#-Sprache lässt sie nicht zu.The CLR always allowed this constraint, but the C# language disallowed it. Die System.Delegate-Einschränkung ermöglicht es Ihnen, Code zu schreiben, der mit Delegaten in einer typsicheren Weise funktioniert.The System.Delegate constraint enables you to write code that works with delegates in a type-safe manner. Der folgende Code definiert eine Erweiterungsmethode, die zwei Delegaten kombiniert, sofern diese vom gleichen Typ sind:The following code defines an extension method that combines two delegates provided they are the same type:

public static TDelegate TypeSafeCombine<TDelegate>(this TDelegate source, TDelegate target)
    where TDelegate : System.Delegate
    => Delegate.Combine(source, target) as TDelegate;

Sie können die oben dargestellte Methode verwenden, um Delegaten vom selben Typ zu kombinieren:You can use the above method to combine delegates that are the same type:

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

Wenn Sie den Kommentar der letzten Zeile entfernen, findet die Kompilation nicht statt.If you uncomment the last line, it won't compile. first und test sind beides Delegattypen, aber sie sind unterschiedliche Delegattypen.Both first and test are delegate types, but they are different delegate types.

EnumerationseinschränkungenEnum constraints

Ab C# 7.3 können Sie auch den System.Enum-Typ als Basisklasseneinschränkung angeben.Beginning in C# 7.3, you can also specify the System.Enum type as a base class constraint. Die CLR lässt diese Einschränkung immer zu, aber die C#-Sprache lässt sie nicht zu.The CLR always allowed this constraint, but the C# language disallowed it. Generika, die System.Enum verwenden, bieten typsichere Programmierung zum Zwischenspeichern von Ergebnissen aus der Verwendung der statischen Methoden in System.Enum.Generics using System.Enum provide type-safe programming to cache results from using the static methods in System.Enum. Im folgenden Beispiel werden alle gültigen Werte für einen Enumerationstyp gefunden, und dann ein Wörterbuch erstellt, das diese Werte ihrer Zeichenfolgendarstellung zuordnet.The following sample finds all the valid values for an enum type, and then builds a dictionary that maps those values to its string representation.

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

Die verwendeten Methoden nutzen die Reflektion, die Auswirkungen auf die Leistung hat.The methods used make use of reflection, which has performance implications. Sie können diese Methode aufrufen, um eine Sammlung zu erstellen, die zwischengespeichert und wiederverwendet wird, anstatt die Aufrufe zu wiederholen, die eine Reflektion erfordern.You can call this method to build a collection that is cached and reused rather than repeating the calls that require reflection.

Sie könnten dies wie im folgenden Beispiel gezeigt verwenden, um eine Enumeration und ein Wörterbuch der Werte und Namen zu erstellen:You could use it as shown in the following sample to create an enum and build a dictionary of its values and names:

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

Siehe auchSee Also