where (generischer Typconstraint) (C#-Referenz)

In einer generischen Typdefinition wird die where-Klausel verwendet, um Constraints für Typen anzugeben, die als Argumente für einen Typenparameter in generischen Typen, Methoden, Delegaten oder lokalen Funktionen verwendet werden können. Constraints können Schnittstellen und Basisklassen angeben oder einen generischen Typ als Verweis-, Wert- oder nicht verwalteten Typ anfordern. Sie deklarieren die Funktionen, die das Typargument aufweisen muss.

So können Sie beispielsweise eine generische Klasse erstellen, AGenericClass, deren Typparameter T die Schnittstelle IComparable<T> implementiert:

public class AGenericClass<T> where T : IComparable<T> { }

Hinweis

Weitere Informationen über die where-Klausel in einem Abfrageausdruck finden Sie unter where-Klausel.

Die where-Klausel kann auch einen Basisklassenconstraint enthalten. Der Basisklassenconstraint gibt an, dass ein Typ, der als Typargument für den generischen Typ verwendet wird, über die angegebene Klasse als Basisklasse verfügen oder diese Basisklasse sein muss. Wenn ein Basisklassenconstraint verwendet wird, muss er vor jedem anderen Constraint für den Typparameter angezeigt werden. Einige Typen sind nicht als Basisklassenconstraints zulässig: Object, Array und ValueType. Vor C# 7.3 waren Enum, Delegate und MulticastDelegate ebenfalls nicht als Basisklassenconstraints zulässig. Das folgende Beispiel zeigt die Typen, die jetzt als Basisklasse angegeben werden können:

public class UsingEnum<T> where T : System.Enum { }

public class UsingDelegate<T> where T : System.Delegate { }

public class Multicaster<T> where T : System.MulticastDelegate { }

In einem Nullable-Kontext in C# 8.0 und höher wird die NULL-Zulässigkeit des Basisklassentyps erzwungen. Wenn die Basisklasse ein Non-Nullable-Typ ist (z. B. Base), muss das Typargument ein Non-Nullable-Typ sein. Ist die Basisklasse ein Nullable-Typ (z. B. Base?), muss das Typargument ein Nullable- oder Non-Nullable-Verweistyp sein. Der Compiler gibt eine Warnung aus, wenn das Typargument ein Nullable-Verweistyp ist und die Basisklasse ein Non-Nullable-Typ.

Die where-Klausel kann angeben, ob der Typ class oder struct ist. Aufgrund des struct-Constraints ist die Angabe eines Basisklassenconstraints von System.ValueType nicht notwendig. Der System.ValueType-Typ darf nicht als Basisklassenconstraint verwendet werden. Im folgenden Beispiel werden die class- und struct-Constraints dargestellt:

class MyClass<T, U>
    where T : class
    where U : struct
{ }

In einem Nullable-Kontext in C# 8.0 und höher erfordert der class-Constraint einen Non-Nullable-Verweistyp. Um Nullable-Verweistypen zuzulassen, verwenden Sie den class?-Constraint, der sowohl Nullable- als auch Non-Nullable-Verweistypen zulässt.

Die where-Klausel kann den notnull-Constraint enthalten. Der notnull-Constraint begrenzt den Typparameter auf Nicht-Nullable-Typen. Bei diesem Typ kann es sich um einen Werttyp oder einen Non-Nullable-Verweistyp handeln. Der notnull-Constraint ist ab C  8.0 für Code verfügbar, der in einem nullable enable-Kontext kompiliert wird. Im Gegensatz zu anderen Constraints generiert der Compiler eine Warnung statt eines Fehlers, wenn ein Typargument den notnull-Constraint verletzt. Warnungen werden nur in einem nullable enable-Kontext generiert.

Das Addition von Nullable-Verweistypen führt zu einer potenziellen Mehrdeutigkeit in der Bedeutung von T? in generischen Methoden. Wenn T ein struct ist, ist T? identisch mit System.Nullable<T>. Wenn jedoch T ein Verweistyp ist, bedeutet T?, dass null ein gültiger Wert ist. Die Mehrdeutigkeit entsteht, da überschreibende Methoden keine Einschränkungen enthalten können. Die neue default Einschränkung löst diese Mehrdeutigkeit auf. Sie fügen sie hinzu, wenn eine Basisklasse oder Schnittstelle zwei Überladungen einer Methode deklariert, eine, die die struct Einschränkung angibt, und eine, für die weder die struct- oder class-Einschränkung angewendet wurde:

public abstract class B
{
    public void M<T>(T? item) where T : struct { }
    public abstract void M<T>(T? item);

}

Sie verwenden die default -Einschränkung, um anzugeben, dass die abgeleitete Klasse die Methode ohne die Einschränkung in der abgeleiteten Klasse oder explizite Schnittstellenimplementierung überschreibt. Sie ist nur für Methoden gültig, die Basismethoden oder explizite Schnittstellenimplementierungen überschreiben:

public class D : B
{
    // Without the "default" constraint, the compiler tries to override the first method in B
    public override void M<T>(T? item) where T : default { }
}

Wichtig

Generische Deklarationen, die den notnull-Constraint enthalten, können in einem Kontext verwendet werden, in dem nicht bekannt ist, ob NULL-Werte zugelassen sind, aber der Compiler erzwingt den Constraint nicht.

#nullable enable
    class NotNullContainer<T>
        where T : notnull
    {
    }
#nullable restore

Die where-Klausel kann auch einen unmanaged-Constraint einschließen. Der unmanaged-Constraint schränkt den Typparameter auf Typen ein, die als nicht verwaltete Typen bekannt sind. Der unmanaged-Constraint erleichtert das Schreiben von Interop-Code in C# auf niedriger Ebene. Dieser Constraint ermöglicht wiederverwendbare Routinen für alle nicht verwalteten Typen. Der unmanaged-Constraint kann nicht mit dem class- oder struct-Constraint kombiniert werden. Der unmanaged-Constraint erzwingt, dass der Typ struct sein muss:

class UnManagedWrapper<T>
    where T : unmanaged
{ }

Die where-Klausel kann auch einen new()-Konstruktorconstraint einschließen. Dieser Constraint ermöglicht das Erstellen einer Instanz eines Typparameters unter Verwendung des new-Operators. Der new()-Constraint informiert den Compiler, dass jedes angegebene Typargument über einen zugänglichen parameterlosen Konstruktor verfügen muss. Zum Beispiel:

public class MyGenericClass<T> where T : IComparable<T>, new()
{
    // The following line is not possible without new() constraint:
    T item = new T();
}

Der new()-Constraint wird in der where-Klausel als Letztes angezeigt. Der new()-Constraint kann nicht mit dem struct- oder unmanaged-Constraint kombiniert werden. Alle Typen, die diese Constraints erfüllen, müssen einen zugänglichen parameterlosen Konstruktor aufweisen, wodurch der new()-Constraint redundant wird.

Bei mehreren Typparametern müssen Sie für jeden davon eine eigene where-Klausel verwenden, z.B.:

public interface IMyInterface { }

namespace CodeExample
{
    class Dictionary<TKey, TVal>
        where TKey : IComparable<TKey>
        where TVal : IMyInterface
    {
        public void Add(TKey key, TVal val) { }
    }
}

Sie können auch Constraints wie folgt an Typparameter generischer Methoden anfügen:

public void MyMethod<T>(T t) where T : IMyInterface { }

Beachten Sie, dass die Syntax zum Beschreiben der Parameterconstraints für Delegaten mit der Syntax von Methoden identisch ist:

delegate T MyDelegate<T>() where T : new();

Informationen zu generischen Delegaten finden Sie unter Generic Delegates (Generische Delegaten).

Weitere Informationen zur Syntax und der Verwendung von Constraints finden Sie unter Constraints für Typparameter.

C#-Sprachspezifikation

Weitere Informationen erhalten Sie unter C#-Sprachspezifikation. Die Sprachspezifikation ist die verbindliche Quelle für die Syntax und Verwendung von C#.

Siehe auch