where (restrição de tipo genérico) (Referência de C#)

A cláusula where em uma definição genérica especifica restrições sobre os tipos que são usados como argumentos para parâmetros de tipo em um tipo genérico, método, delegado ou função local. Restrições podem especificar interfaces, classes base ou exigir que um tipo genérico seja uma referência, valor ou tipo não gerenciado. Eles declaram recursos que o argumento de tipo deve ter e devem ser colocados após qualquer classe base declarada ou interfaces implementadas.

Por exemplo, você pode declarar uma classe genérica, AGenericClass, de modo que o parâmetro de tipo T implementa a interface IComparable<T>:

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

Observação

Para obter mais informações sobre a cláusula where em uma expressão de consulta, consulte Cláusula where.

A cláusula where também pode incluir uma restrição de classe base. A restrição de classe base declara que um tipo a ser usado como um argumento de tipo para aquele tipo genérico tem a classe especificada como uma classe base ou é essa classe base. Se a restrição de classe base for usada, ela deverá aparecer antes de qualquer outra restrição nesse parâmetro de tipo. Alguns tipos não têm permissão como uma restrição de classe base: Object, Array e ValueType. O exemplo a seguir mostra os tipos que agora podem ser especificados como classe 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 { }

Em um contexto anulável, a nulidade do tipo de classe base é imposta. Se a classe base não for anulável (por exemplo Base), o argumento de tipo deverá ser não anulável. Se a classe base for anulável (por exemplo, Base?), o argumento de tipo poderá ser um tipo de referência anulável ou não anulável. O compilador emitirá um aviso se o argumento de tipo for um tipo de referência anulável quando a classe base não for anulável.

A cláusula where pode especificar que o tipo é um class ou um struct. A restrição struct elimina a necessidade de especificar uma restrição de classe base de System.ValueType. O tipo System.ValueType não pode ser usado como uma restrição de classe base. O exemplo a seguir mostra as restrições class e struct:

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

Em um contexto anulável, a restrição class requer um tipo para ser um tipo de referência não anulável. Para permitir tipos de referência anuláveis, use a restrição class?, que permite tipos de referência anuláveis e não anuláveis.

A cláusula where pode incluir a restrição notnull. A restrição notnull limita o parâmetro de tipo a tipos não anuláveis. O tipo pode ser um tipo de valor ou um tipo de referência não anulável. A restrição notnull está disponível para código compilado em um nullable enable contexto. Ao contrário de outras restrições, se um argumento de tipo violar a restrição notnull, o compilador gerará um aviso em vez de um erro. Os avisos são gerados apenas em um contexto nullable enable.

A adição de tipos de referência anuláveis introduz uma ambiguidade potencial no significado de T? em métodos genéricos. Se T for um struct, T? é o mesmo que System.Nullable<T>. No entanto, se T for um tipo de referência, T? significa que null é um valor válido. A ambiguidade surge porque os métodos de substituição não podem incluir restrições. A nova restrição default resolve essa ambiguidade. Você a adicionará quando uma classe base ou interface declarar duas sobrecargas de um método, uma que especifica a restrição struct e outra que não tenha a restrição struct ou class aplicada:

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

}

Você usa a restrição default para especificar que sua classe derivada substitui o método sem a restrição em sua classe derivada ou a implementação de interface explícita. Ele só é válido em métodos que substituem métodos base ou implementações de interface 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

Declarações genéricas que incluem a restrição notnull podem ser usadas em um contexto alheio anulável, mas o compilador não impõe a restrição.

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

A cláusula where também pode incluir uma restrição unmanaged. A restrição unmanaged limita o parâmetro de tipo a tipos conhecidos como tipos não gerenciados. Usando a restrição unmanaged, é mais fácil escrever o código de interoperabilidade de nível baixo em C#. Essa restrição habilita rotinas reutilizáveis em todos os tipos não gerenciados. A restrição unmanaged não pode ser combinada à restrição class ou struct. A restrição unmanaged impõe que o tipo deve ser um struct:

class UnManagedWrapper<T>
    where T : unmanaged
{ }

A cláusula where também pode incluir uma restrição de construtor, new(). Essa restrição torna possível criar uma instância de um parâmetro de tipo usando o operador new. A restrição new() informa o compilador que qualquer argumento de tipo fornecido deve ter um construtor sem parâmetros acessível. Por exemplo:

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

A restrição new() aparece por último na cláusula where. A restrição new() não pode ser combinada às restrições struct ou unmanaged. Todos os tipos que satisfazem as restrições devem ter um construtor sem parâmetros acessível, tornando a restrição new() redundante.

Com vários parâmetros de tipo, use uma cláusula where para cada parâmetro de tipo, por exemplo:

public interface IMyInterface { }

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

Você também pode anexar restrições a parâmetros de tipo de métodos genéricos, como mostrado no exemplo a seguir:

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

Observe que a sintaxe para descrever as restrições de parâmetro de tipo em delegados é a mesma que a dos métodos:

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

Para obter informações sobre delegados genéricos, consulte Delegados genéricos.

Para obter detalhes sobre a sintaxe e o uso de restrições, consulte Restrições a parâmetros de tipo.

Especificação da linguagem C#

Para obter mais informações, consulte a Especificação da linguagem C#. A especificação da linguagem é a fonte definitiva para a sintaxe e o uso de C#.

Confira também