where (restricción de tipo genérico) (Referencia de C#)

La cláusula where en una definición genérica especifica restricciones en los tipos que se usan como argumentos para los parámetros de tipo en un tipo genérico, método, delegado o función local. Las restricciones pueden especificar interfaces o clases base, o bien requerir que un tipo genérico sea una referencia, un valor o un tipo no administrado. Declaran las funcionalidades que debe tener el argumento de tipo, y deben colocarse después de cualquier clase base declarada o interfaces implementadas.

Por ejemplo, se puede declarar una clase genérica, AGenericClass, de modo que el parámetro de tipo T implemente la interfaz IComparable<T>:

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

Nota:

Para obtener más información sobre la cláusula where en una expresión de consulta, vea where (Cláusula).

La cláusula where también puede incluir una restricción de clase base. La restricción de clase base indica que un tipo que se va a usar como argumento de tipo para ese tipo genérico tiene la clase especificada como clase base, o bien es la clase base. Si se usa la restricción de clase base, debe aparecer antes que cualquier otra restricción de ese parámetro de tipo. Algunos tipos no están permitidos como restricción de clase base: Object, Array y ValueType. En el ejemplo siguiente se muestran los tipos que ahora se pueden especificar como clase 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 { }

En un contexto que admite un valor NULL, se aplica la nulabilidad del tipo de clase base. Si la clase base no acepta valores NULL (por ejemplo, Base), el argumento de tipo no debe aceptar valores NULL. Si la clase base admite un valor NULL (por ejemplo, Base?), el argumento de tipo puede ser un tipo de referencia que acepte o no valores NULL. El compilador emite una advertencia si el argumento de tipo es un tipo de referencia que admite un valor NULL cuando la clase base no acepta valores NULL.

La cláusula where puede especificar que el tipo es class o struct. La restricción struct elimina la necesidad de especificar una restricción de clase base de System.ValueType. El tipo System.ValueType no se puede usar como restricción de clase base. En el ejemplo siguiente se muestran las restricciones class y struct:

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

En un contexto que admite un valor NULL, la restricción class requiere que un tipo sea un tipo de referencia que no acepta valores NULL. Para permitir tipos de referencia que admitan un valor NULL, use la restricción class?, que permite tipos de referencia que aceptan y que no aceptan valores NULL.

La cláusula where puede incluir la restricción notnull. La restricción notnull limita el parámetro de tipo a tipos que no aceptan valores NULL. El tipo puede ser un tipo de valor o un tipo de referencia que no acepta valores NULL. La restricción notnull está disponible para el código compilado en un contexto nullable enable. A diferencia de otras restricciones, si un argumento de tipo infringe la restricción notnull, el compilador genera una advertencia en lugar de un error. Las advertencias solo se generan en un contexto nullable enable.

La incorporación de tipos de referencia que aceptan valores NULL introduce una ambigüedad potencial en el significado de T? en los métodos genéricos. Si T es un elemento struct, T? es igual que System.Nullable<T>. Sin embargo, si T es un tipo de referencia, T? significa que null es un valor válido. La ambigüedad surge porque invalidar métodos no puede incluir restricciones. La restricción default nueva resuelve esta ambigüedad. Se agregará cuando una clase base o interfaz declare dos sobrecargas de un método; una que especifica la restricción struct, y otra que no tiene aplicada la restricción struct ni la class:

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

}

La restricción default se usa para especificar que la clase derivada invalida el método sin la restricción en la clase derivada o la implementación de interfaz explícita. Solo es válido en métodos que invalidan métodos base o implementaciones de interfaz explícitas:

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

Importante

Las declaraciones genéricas que incluyen la restricción notnull se pueden usar en un contexto donde se desconoce que se aceptan valores NULL, pero el compilador no aplica la restricción.

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

La cláusula where también podría incluir una restricción unmanaged. La restricción unmanaged limita el parámetro de tipo a los tipos conocidos como tipos no administrados. La restricción unmanaged hace que sea más fácil escribir código de interoperabilidad de bajo nivel en C#. Esta restricción habilita las rutinas reutilizables en todos los tipos no administrados. La restricción unmanaged no se puede combinar con las restricciones class o struct. La restricción unmanaged exige que el tipo sea struct:

class UnManagedWrapper<T>
    where T : unmanaged
{ }

La cláusula where también podría incluir una restricción de constructor, new(). Esta restricción hace posible crear una instancia de un parámetro de tipo con el operador new. La restricción new() permite que el compilador sepa que cualquier argumento de tipo especificado debe tener accesible un constructor sin parámetros. Por ejemplo:

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

La restricción new() aparece en último lugar en la cláusula where. La restricción new() no se puede combinar con las restricciones struct o unmanaged. Todos los tipos que cumplan esas restricciones deben tener un constructor sin parámetros accesible, lo que hace que la restricción new() sea redundante.

Con varios parámetros de tipo, use una cláusula where para cada parámetro de tipo, por ejemplo:

public interface IMyInterface { }

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

También puede asociar restricciones a parámetros de tipo de métodos genéricos, como se muestra en el ejemplo siguiente:

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

Observe que la sintaxis para describir las restricciones de parámetro de tipo en delegados es igual que la de métodos:

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

Para obtener información sobre los delegados genéricos, vea Delegados genéricos.

Para obtener más información sobre la sintaxis y el uso de restricciones, vea Restricciones de tipos de parámetros.

Especificación del lenguaje C#

Para obtener más información, consulte la Especificación del lenguaje C#. La especificación del lenguaje es la fuente definitiva de la sintaxis y el uso de C#.

Consulte también