Restrições a parâmetros de tipo (Guia de Programação em C#)Constraints on type parameters (C# Programming Guide)

Restrições informam o compilador sobre as funcionalidades que um argumento de tipo deve ter.Constraints inform the compiler about the capabilities a type argument must have. Sem nenhuma restrição, o argumento de tipo poderia ser qualquer tipo.Without any constraints, the type argument could be any type. O compilador pode assumir somente os membros de System.Object, que é a classe base definitiva para qualquer tipo .NET.The compiler can only assume the members of System.Object, which is the ultimate base class for any .NET type. Para obter mais informações, consulte Por que usar restrições.For more information, see Why use constraints. Se o código de cliente tentar criar uma instância da classe usando um tipo não permitido por uma restrição, o resultado será um erro em tempo de compilação.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. Restrições são especificadas usando a palavra-chave contextual where.Constraints are specified by using the where contextual keyword. A tabela a seguir lista os sete tipos de restrições:The following table lists the seven types of constraints:

RestriçãoConstraint DescriçãoDescription
where T : struct O argumento de tipo deve ser um tipo de valor não anulável.The type argument must be a non-nullable value type. Para obter informações sobre tipos de valor anulável, consulte tipos de valor anulável.For information about nullable value types, see Nullable value types. Como todos os tipos de valor têm um construtor acessível sem parâmetros, a restrição struct implica a restrição new() e não pode ser combinada com a restrição new().Because all value types have an accessible parameterless constructor, the struct constraint implies the new() constraint and can't be combined with the new() constraint. Você também não pode combinar a restrição de struct com a restrição unmanaged.You also cannot combine the struct constraint with the unmanaged constraint.
where T : class O argumento de tipo deve ser um tipo de referência.The type argument must be a reference type. Essa restrição se aplica também a qualquer classe, interface, delegado ou tipo de matriz.This constraint applies also to any class, interface, delegate, or array type.
where T : notnull O argumento de tipo deve ser um tipo não anulável.The type argument must be a non-nullable type. O argumento pode ser um tipo de referência não anulável em C# 8,0 ou posterior, ou um tipo de valor não anulável.The argument can be a non-nullable reference type in C# 8.0 or later, or a not nullable value type. Essa restrição se aplica também a qualquer classe, interface, delegado ou tipo de matriz.This constraint applies also to any class, interface, delegate, or array type.
where T : unmanaged O argumento de tipo deve ser um tipo não- gerenciadonão anulável.The type argument must be a non-nullable unmanaged type. A restrição unmanaged implica a restrição struct e não pode ser combinada com as restrições struct ou new().The unmanaged constraint implies the struct constraint and can't be combined with either the struct or new() constraints.
where T : new() O argumento de tipo deve ter um construtor público sem parâmetros.The type argument must have a public parameterless constructor. Quando usado em conjunto com outras restrições, a restrição new() deve ser a última a ser especificada.When used together with other constraints, the new() constraint must be specified last. A restrição new() não pode ser combinada com as restrições struct e unmanaged.The new() constraint can't be combined with the struct and unmanaged constraints.
where T : <nome da classe base >where T : <base class name> O argumento de tipo deve ser ou derivar da classe base especificada.The type argument must be or derive from the specified base class.
where T : <nome da interface >where T : <interface name> O argumento de tipo deve ser ou implementar a interface especificada.The type argument must be or implement the specified interface. Várias restrições de interface podem ser especificadas.Multiple interface constraints can be specified. A interface de restrição também pode ser genérica.The constraining interface can also be generic.
where T : U O argumento de tipo fornecido para T deve ser ou derivar do argumento fornecido para U.The type argument supplied for T must be or derive from the argument supplied for U.

Por que usar restriçõesWhy use constraints

Ao restringir o parâmetro de tipo, aumenta-se a quantidade de operações e chamadas de método permitidas àqueles com suporte do tipo de restrição e de todos os tipos de sua hierarquia de herança.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. Ao projetar classes ou métodos genéricos, se você estiver executando qualquer operação nos membros genéricos além da atribuição simples ou chamando quaisquer métodos sem suporte pelo System.Object, precisará aplicar restrições ao parâmetro de tipo.When you design generic classes or methods, if you'll be performing any operation on the generic members beyond simple assignment or calling any methods not supported by System.Object, you'll have to apply constraints to the type parameter. Por exemplo, a restrição de classe base informa ao compilador que somente os objetos desse tipo ou derivados desse tipo serão usados como argumentos de tipo.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. Uma vez que o compilador tiver essa garantia, ele poderá permitir que métodos desse tipo sejam chamados na classe genérica.Once the compiler has this guarantee, it can allow methods of that type to be called in the generic class. O exemplo de código a seguir demonstra a funcionalidade que pode ser adicionada à classe GenericList<T> (em Introdução aos Genéricos) ao aplicar uma restrição de classe base.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;
    }
}

A restrição permite que a classe genérica use a propriedade Employee.Name.The constraint enables the generic class to use the Employee.Name property. A restrição especifica que todos os itens do tipo T são um objeto Employee ou um objeto que herda de Employee.The constraint specifies that all items of type T are guaranteed to be either an Employee object or an object that inherits from Employee.

Várias restrições podem ser aplicadas ao mesmo parâmetro de tipo e as restrições em si podem ser tipos genéricos, da seguinte maneira: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()
{
    // ...
}

Ao aplicar a restrição where T : class, evite os operadores == e != no parâmetro de tipo, pois esses operadores testarão somente a identidade de referência e não a igualdade de valor.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. Esse comportamento ocorrerá mesmo se esses operadores forem sobrecarregados em um tipo usado como argumento.This behavior occurs even if these operators are overloaded in a type that is used as an argument. O código a seguir ilustra esse ponto; a saída é false, muito embora a classe String sobrecarregue o operador ==.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);
}

O compilador só sabe que T é um tipo de referência em tempo de compilação e deve usar os operadores padrão que são válidos para todos os tipos de referência.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. Caso seja necessário testar a igualdade de valor, a maneira recomendada é também aplicar a restrição where T : IEquatable<T> ou where T : IComparable<T> e implementar a interface em qualquer classe que seja usada para construir a classe genérica.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.

Restringindo vários parâmetrosConstraining multiple parameters

É possível aplicar restrições a vários parâmetros e várias restrições a um único parâmetro, conforme mostrado no exemplo a seguir: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()
{ }

Parâmetros de tipo não associadoUnbounded type parameters

Os parâmetros de tipo que não têm restrições, como o T na classe pública SampleClass<T>{}, são denominados “parâmetros de tipo não associado”.Type parameters that have no constraints, such as T in public class SampleClass<T>{}, are called unbounded type parameters. Os parâmetros de tipo não associado têm as seguintes regras:Unbounded type parameters have the following rules:

  • Os operadores de != e == não podem ser usados porque não há garantia de que o argumento de tipo concreto dará suporte a esses operadores.The != and == operators can't be used because there's no guarantee that the concrete type argument will support these operators.
  • Eles podem ser convertidos para e de System.Object ou explicitamente convertidos para qualquer tipo de interface.They can be converted to and from System.Object or explicitly converted to any interface type.
  • Você pode compará-los com nulo.You can compare them to null. Se um parâmetro não associado for comparado a null, a comparação sempre retornará false se o argumento de tipo for um tipo de valor.If an unbounded parameter is compared to null, the comparison will always return false if the type argument is a value type.

Parâmetros de tipo como restriçõesType parameters as constraints

O uso de um parâmetro de tipo genérico como uma restrição será útil quando uma função membro com parâmetro de tipo próprio tiver que restringir esse parâmetro para o parâmetro de tipo do tipo recipiente, conforme mostrado no exemplo a seguir: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 {/*...*/}
}

No exemplo anterior, T é uma restrição de tipo no contexto do método Add e um parâmetro de tipo não associado no contexto da classe List.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.

Parâmetros de tipo também podem ser usados como restrições em definições de classe genérica.Type parameters can also be used as constraints in generic class definitions. O parâmetro de tipo deve ser declarado entre colchetes angulares junto com quaisquer outros parâmetros de tipo: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 { }

A utilidade dos parâmetros de tipo como restrições com classes genéricas é limitada, pois o compilador não pode presumir nada sobre o parâmetro de tipo, exceto que ele deriva de System.Object.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. Use parâmetros de tipo como restrições em classes genéricas em cenários nos quais deseja impor uma relação de herança entre dois parâmetros de tipo.Use type parameters as constraints on generic classes in scenarios in which you want to enforce an inheritance relationship between two type parameters.

Restrição não nulaNotNull constraint

A partir C# do 8,0, você pode usar a restrição notnull para especificar que o argumento de tipo deve ser um tipo de valor não anulável ou um tipo de referência não anulável.Beginning with C# 8.0, you can use the notnull constraint to specify that the type argument must be a non-nullable value type or non-nullable reference type. A restrição notnull só pode ser usada em um contexto de nullable enable.The notnull constraint can only be used in a nullable enable context. O compilador gerará um aviso se você adicionar a restrição de notnull em um contexto de alheios anulável.The compiler generates a warning if you add the notnull constraint in a nullable oblivious context.

Ao contrário de outras restrições, quando um argumento de tipo viola a restrição de notnull, o compilador gera um aviso quando esse código é compilado em um contexto de nullable enable.Unlike other constraints, when a type argument violates the notnull constraint, the compiler generates a warning when that code is compiled in a nullable enable context. Se o código for compilado em um contexto alheios anulável, o compilador não gerará nenhum aviso ou erro.If the code is compiled in a nullable oblivious context, the compiler doesn't generate any warnings or errors.

Restrição não gerenciadaUnmanaged constraint

A partir C# do 7,3, você pode usar a restrição unmanaged para especificar que o parâmetro de tipo deve ser um tipo não- gerenciadonão anulável.Beginning with C# 7.3, you can use the unmanaged constraint to specify that the type parameter must be a non-nullable unmanaged type. A restrição unmanaged permite que você escreva rotinas reutilizáveis para trabalhar com tipos que podem ser manipulados como blocos de memória, conforme mostrado no exemplo a seguir: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;
}

O método anterior deve ser compilado em um contexto unsafe porque ele usa o operador sizeof em um tipo não conhecido como um tipo interno.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. Sem a restrição unmanaged, o operador sizeof não está disponível.Without the unmanaged constraint, the sizeof operator is unavailable.

A restrição unmanaged implica a restrição struct e não pode ser combinada com ela.The unmanaged constraint implies the struct constraint and can't be combined with it. Como a restrição struct implica a restrição new(), a restrição unmanaged também não pode ser combinada com a restrição new().Because the struct constraint implies the new() constraint, the unmanaged constraint can't be combined with the new() constraint as well.

Restrições de delegadoDelegate constraints

Também começando com o C# 7.3, você pode usar System.Delegate ou System.MulticastDelegate como uma restrição de classe base.Also beginning with C# 7.3, you can use System.Delegate or System.MulticastDelegate as a base class constraint. O CLR sempre permitia essa restrição, mas a linguagem C# não a permite.The CLR always allowed this constraint, but the C# language disallowed it. A restrição System.Delegate permite que você escreva código que funcione com delegados de uma maneira fortemente tipada.The System.Delegate constraint enables you to write code that works with delegates in a type-safe manner. O código a seguir define um método de extensão que combina dois delegados, desde que eles sejam do mesmo tipo:The following code defines an extension method that combines two delegates provided they're the same type:

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

Você pode usar o método acima para combinar delegados que são do mesmo tipo: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);

Se você remover a marca de comentário na última linha, ela não será compilada.If you uncomment the last line, it won't compile. Tanto first quanto test são tipos delegados, mas são tipos delegados diferentes.Both first and test are delegate types, but they're different delegate types.

Restrições de enumEnum constraints

Começando com o C# 7.3, você também pode especificar o tipo System.Enum como uma restrição de classe base.Beginning in C# 7.3, you can also specify the System.Enum type as a base class constraint. O CLR sempre permitia essa restrição, mas a linguagem C# não a permite.The CLR always allowed this constraint, but the C# language disallowed it. Genéricos usando System.Enum fornecem programação fortemente tipada para armazenar em cache os resultados do uso de métodos estáticos em System.Enum.Generics using System.Enum provide type-safe programming to cache results from using the static methods in System.Enum. O exemplo a seguir localiza todos os valores válidos para um tipo enum e, em seguida, cria um dicionário que mapeia esses valores para sua representação de cadeia de caracteres.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;
}

Os métodos usados para fazer uso da reflexão, que tem implicações de desempenho.The methods used to make use of reflection, which has performance implications. Você pode chamar esse método para criar uma coleção que é armazenada em cache e reutilizada, em vez de repetir as chamadas que exigem reflexão.You can call this method to build a collection that is cached and reused rather than repeating the calls that require reflection.

Você pode usá-lo conforme mostrado no exemplo a seguir para criar uma enum e compilar um dicionário de seus nomes e valores: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}");

Veja tambémSee also