where (contrainte de type générique) (Référence C#)

La clause where dans une définition générique spécifie des contraintes sur les types qui sont utilisés comme arguments pour les paramètres de type d’un type générique, d’une méthode, d’un délégué ou d’une fonction locale. Les contraintes peuvent spécifier des interfaces ou des classes de base, ou nécessiter un type générique comme référence, valeur ou type non managé. Elles déclarent des fonctionnalités que l’argument de type doit avoir, et elles doivent être placées après toute classe de base déclarée ou interfaces implémentées.

Vous pouvez, par exemple, déclarer une classe générique, AGenericClass, de telle sorte que le paramètre de type T implémente l’interface IComparable<T> :

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

Notes

Pour plus d’informations sur la clause where dans une expression de requête, consultez where, clause.

La clause where peut également inclure une contrainte de classe de base. La contrainte de classe de base indique qu’un type à utiliser comme argument de type pour ce type générique a la classe spécifiée comme classe de base ou est la classe de base. Si la contrainte de classe de base est utilisée, elle doit apparaître avant toute autre contrainte sur ce paramètre de type. Certains types ne sont pas autorisés comme contrainte de classe de base : Object, Array et ValueType. L’exemple suivant montre les types qui peuvent maintenant être spécifiés comme classe de base :

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

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

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

Dans un contexte nullable, la possibilité de nullabilité du type de classe de base est appliquée. Si la classe de base est non-nullable (par exemple Base), l’argument de type doit être non-nullable. Si la classe de base peut accepter la valeur Null (par exemple Base?), l’argument de type peut être un type de référence pouvant accepter la valeur Null ou non-nullable. Le compilateur émet un avertissement si l’argument de type est un type référence nullable lorsque la classe de base est non-nullable.

La clause where peut spécifier que le type est une class ou un struct. La contrainte struct supprime la nécessité de spécifier une contrainte de classe de base de System.ValueType. Le type System.ValueType ne peut pas être utilisé comme contrainte de classe de base. L’exemple suivant montre les contraintes class et struct :

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

Dans un contexte nullable, la contrainte class exige qu’un type soit un type référence non-nullable. Pour autoriser les types référence nullables, utilisez la contrainte class?, qui autorise les types référence nullables et non-nullables.

La clause where peut inclure la contrainte notnull. La contrainte notnull limite le paramètre de type aux types non-nullables. Le type peut être un type de valeur ou un type de référence non-nullable. La contrainte notnull est disponible pour le code compilé dans un nullable enable contexte. Contrairement à d’autres contraintes, si un argument de type enfreint la contrainte notnull, le compilateur génère un avertissement au lieu d’une erreur. Les avertissements sont générés uniquement dans un contexte nullable enable.

L’ajout de types référence nullables introduit une ambiguïté potentielle dans la signification de T? dans les méthodes génériques. Si T est un struct, T? est identique à System.Nullable<T>. Toutefois, si T est un type référence, T? signifie que null est une valeur valide. L’ambiguïté survient parce que les méthodes de substitution ne peuvent pas inclure de contraintes. La nouvelle contrainte default résout cette ambiguïté. Vous l’ajoutez lorsqu’une classe de base ou une interface déclare deux surcharges d’une méthode, l’une qui spécifie la contrainte struct et l’autre qui n’a pas la contrainte struct ou class appliquée :

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

}

Vous utilisez la contrainte default pour spécifier que votre classe dérivée remplace la méthode sans la contrainte dans votre classe dérivée ou l’implémentation d’interface explicite. Elle n’est valide que sur les méthodes qui remplacent les méthodes de base ou les implémentations d’interface explicites :

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

Important

Les déclarations génériques qui incluent la contrainte notnull peuvent être utilisées dans un contexte inconscient nullable, mais le compilateur n’applique pas la contrainte.

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

La clause where peut aussi inclure une contrainte unmanaged. La contrainte unmanaged limite le paramètre de type aux types connus sous le nom de types non managés. La contrainte unmanaged facilite l’écriture de code interop de bas niveau en C#. Cette contrainte permet des routines réutilisables sur tous les types non managés. La contrainte unmanaged ne peut pas être combinée avec la contrainte class ou struct. La contrainte unmanaged exige que le type doit être un struct :

class UnManagedWrapper<T>
    where T : unmanaged
{ }

La clause where peut également inclure une contrainte de constructeur, new(). Cette contrainte permet de créer une instance d’un paramètre de type à l’aide de l’opérateur new. La contrainte new() indique au compilateur que tout argument de type fourni doit avoir un constructeur accessible sans paramètre. Par exemple :

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

La contrainte new() apparaît en dernier dans la clause where, sauf si elle est autorisée par l’anti-contrainte allows ref struct. La contrainte new() ne peut pas être combinée avec les contraintes struct ou unmanaged. Tous les types répondant à ces contraintes doivent avoir un constructeur sans paramètre accessible, ce qui rend la contrainte new() redondante.

Cette anti-contrainte déclare que l’argument de type pour T peut être un type ref struct. Par exemple :

public class GenericRefStruct<T> where T : allows ref struct
{
    // Scoped is allowed because T might be a ref struct
    public void M(scoped T parm)
    {

    }
}

La méthode ou le type générique doit obéir aux règles de sécurité de référence pour toute instance de T, car il est possible que ce soit un ref struct. La clause allows ref struct ne peut pas être combinée avec la contrainte class ou class?. L’anti-contrainte allows ref struct doit suivre toutes les contraintes pour cet argument de type.

Avec plusieurs paramètres de type, utilisez une clause where pour chaque paramètre de type, par exemple :

public interface IMyInterface { }

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

Vous pouvez également joindre des contraintes aux paramètres de type des méthodes génériques, comme montré dans l’exemple suivant :

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

Notez que la syntaxe décrivant les contraintes de paramètre de type sur les délégués est la même que celle des méthodes :

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

Pour plus d’informations sur les délégués génériques, consultez Délégués génériques.

Pour plus d’informations sur la syntaxe et l’utilisation de contraintes, consultez Contraintes sur les paramètres de type.

spécification du langage C#

Pour plus d'informations, voir la spécification du langage C#. La spécification du langage est la source de référence pour la syntaxe C# et son utilisation.

Voir aussi