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.The type argument must be a value type. Qualquer valor de tipo com exceção de Nullable<T> pode ser especificado.Any value type except Nullable<T> can be specified. Para obter mais informações sobre tipos que permitem valor nulo, consulte Tipos que permitem valor nulo.For more information about nullable types, see Nullable types.
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 : unmanaged O argumento de tipo deve ser um tipo não gerenciado.The type argument must be an unmanaged type.
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.
where T : <nome de 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.

Algumas das restrições são mutuamente exclusivas.Some of the constraints are mutually exclusive. Todos os tipos de valor devem ter um construtor sem parâmetros acessível.All value types must have an accessible parameterless constructor. A restrição struct implica a restrição new() e a restrição new() não pode ser combinada com a restrição struct.The struct constraint implies the new() constraint and the new() constraint cannot be combined with the struct constraint. A restrição unmanaged implica a restrição struct.The unmanaged constraint implies the struct constraint. A restrição unmanaged não pode ser combinada às restrições struct ou new().The unmanaged constraint cannot be combined with either the struct or new() constraints.

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, caso deseje executar qualquer operação nos membros genéricos (além da simples atribuição) ou chamar métodos sem suporte do System.Object, será necessário aplicar restrições ao parâmetro de tipo.When you design generic classes or methods, if you will be performing any operation on the generic members beyond simple assignment or calling any methods not supported by System.Object, you will 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 sabe apenas que T é um tipo de referência no tempo de compilação e deve usar os operadores padrã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 != e == não podem ser usados, pois não há garantia de que o argumento de tipo concreto oferecerá suporte a esses operadores.The != and == operators cannot be used because there is 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 gerenciadaUnmanaged constraint

Começando com o C# 7.3, você pode usar a restrição unmanaged para especificar que o parâmetro de tipo deve ser um tipo não gerenciado.Beginning with C# 7.3, you can use the unmanaged constraint to specify that the type parameter must be an 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.

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 fornecidos que são do mesmo tipo:The following code defines an extension method that combines two delegates provided they are 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 de delegado, mas são tipos diferentes de delegado.Both first and test are delegate types, but they are 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 fazem uso de reflexão, o que tem implicações de desempenho.The methods used 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}");

Consulte tambémSee also