Restricciones de tipos de parámetros (Guía de programación de C#)Constraints on type parameters (C# Programming Guide)

Las restricciones informan al compilador sobre las capacidades que debe tener un argumento de tipo.Constraints inform the compiler about the capabilities a type argument must have. Sin restricciones, el argumento de tipo puede ser cualquier tipo.Without any constraints, the type argument could be any type. El compilador solo puede suponer los miembros de Object, que es la clase base fundamental de los tipos .NET.The compiler can only assume the members of Object, which is the ultimate base class for any .NET type. Para más información, vea Por qué usar restricciones.For more information, see Why use constraints. Si el código de cliente intenta crear una instancia de su clase mediante un tipo que no se permite por una restricción, el resultado es un error en tiempo de compilación.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. Las restricciones se especifican con la palabra clave contextual where.Constraints are specified by using the where contextual keyword. En la tabla siguiente se muestran los siete tipos de restricciones:The following table lists the seven types of constraints:

RestricciónConstraint DescripciónDescription
where T : struct El argumento de tipo debe ser un tipo de valor.The type argument must be a value type. Cualquier tipo de valor excepto Nullable<T> puede especificarse.Any value type except Nullable<T> can be specified. Para obtener más información sobre los tipos que aceptan valores NULL, vea Tipos que aceptan valores NULL.For more information about nullable types, see Nullable types.
where T : class El argumento de tipo debe ser un tipo de referencia.The type argument must be a reference type. Esta restricción se aplica también a cualquier clase, interfaz, delegado o tipo de matriz.This constraint applies also to any class, interface, delegate, or array type.
where T : unmanaged El argumento de tipo no debe ser un tipo de referencia y no debe contener ningún miembro de tipo de referencia en ningún nivel de anidamiento.The type argument must not be a reference type and must not contain any reference type members at any level of nesting.
where T : new() El argumento de tipo debe tener un constructor sin parámetros público.The type argument must have a public parameterless constructor. Cuando se usa conjuntamente con otras restricciones, la restricción new() debe especificarse en último lugar.When used together with other constraints, the new() constraint must be specified last.
where T : <nombre de clase base>where T : <base class name> El argumento de tipo debe ser o derivarse de la clase base especificada.The type argument must be or derive from the specified base class.
where T : <nombre de interfaz>where T : <interface name> El argumento de tipo debe ser o implementar la interfaz especificada.The type argument must be or implement the specified interface. Pueden especificarse varias restricciones de interfaz.Multiple interface constraints can be specified. La interfaz de restricciones también puede ser genérica.The constraining interface can also be generic.
where T : U El argumento de tipo proporcionado por T debe ser o derivarse del argumento proporcionado para U.The type argument supplied for T must be or derive from the argument supplied for U.

Algunas de estas restricciones son mutuamente excluyentes.Some of the constraints are mutually exclusive. Todos los tipos de valor deben tener un constructor sin parámetros accesible.All value types must have an accessible parameterless constructor. La restricción struct implica la restricción new() y la restricción new() no se puede combinar con la restricción struct.The struct constraint implies the new() constraint and the new() constraint cannot be combined with the struct constraint. La restricción unmanaged implica la restricción struct.The unmanaged constraint implies the struct constraint. La restricción unmanaged no se puede combinar con las restricciones struct o new().The unmanaged constraint cannot be combined with either the struct or new() constraints.

Por qué usar restriccionesWhy use constraints

Al restringir el parámetro de tipo, aumenta el número de operaciones y llamadas al método permitidas a las que se admiten mediante el tipo de restricción y todos los tipos en su jerarquía de herencia.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. Cuando diseña métodos o clases genéricas, si va a realizar una operación en los miembros genéricos más allá de una asignación simple o una llamada a un método que System.Object no admita, tendrá que aplicar restricciones al 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 ejemplo, la restricción de clase base indica al compilador que solo los objetos de este tipo o derivados de este tipo se usarán 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. Una vez que el compilador tenga esta garantía, puede permitir que los métodos de ese tipo se llamen en la clase genérica.Once the compiler has this guarantee, it can allow methods of that type to be called in the generic class. En el ejemplo de código siguiente se muestran las funciones que podemos agregar a la clase GenericList<T> (en Introducción a los genéricos) mediante la aplicación de una restricción de clase 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;
    }
}

La restricción permite que la clase genérica use la propiedad Employee.Name.The constraint enables the generic class to use the Employee.Name property. La restricción especifica que está garantizado que todos los elementos de tipo T sean un objeto Employee u objeto que hereda 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.

Pueden aplicarse varias restricciones en el mismo parámetro de tipo, y las propias restricciones pueden ser tipos genéricos, de la manera siguiente: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()
{
    // ...
}

Al aplicar la restricción where T : class, evite los operadores == y != en el parámetro de tipo porque estos operadores se probarán solo para la identidad de referencia, no para la igualdad 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. Este comportamiento se produce incluso si estos operadores están sobrecargados en un tipo que se usa como un argumento.This behavior occurs even if these operators are overloaded in a type that is used as an argument. En el código siguiente se ilustra este punto; el resultado es False incluso cuando la clase String sobrecarga al 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);
}

El compilador solo sabe que T es un tipo de referencia en tiempo de compilación y debe usar los operadores predeterminados que son válidos para todos los tipos de referencia.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. Si debe probar la igualdad de valor, la manera recomendada también es aplicar la restricción where T : IEquatable<T> o where T : IComparable<T> e implementar esa interfaz en cualquier clase que se usará para construir la clase 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.

Restringir varios parámetrosConstraining multiple parameters

Puede aplicar restricciones a varios parámetros, y varias restricciones a un solo parámetro, como se muestra en el siguiente ejemplo: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 sin enlazarUnbounded type parameters

Los parámetros de tipo que no tienen restricciones, como T en la clase pública SampleClass<T>{}, se denominan parámetros de tipo sin enlazar.Type parameters that have no constraints, such as T in public class SampleClass<T>{}, are called unbounded type parameters. Los parámetros de tipo sin enlazar tienen las reglas siguientes:Unbounded type parameters have the following rules:

  • Los operadores != y == no pueden usarse porque no existe ninguna garantía de que el argumento de tipo concreto admitirá estos operadores.The != and == operators cannot be used because there is no guarantee that the concrete type argument will support these operators.
  • Pueden convertirse a y desde System.Object o convertirse explícitamente en cualquier tipo de interfaz.They can be converted to and from System.Object or explicitly converted to any interface type.
  • Puede compararlos con NULL.You can compare them to null. Si un parámetro sin enlazar se compara con null, la comparación siempre devolverá False si el argumento de tipo es un 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 restriccionesType parameters as constraints

El uso de un parámetro de tipo genérico como una restricción es útil cuando una función de miembro con su propio parámetro de tipo tiene que restringir ese parámetro al parámetro de tipo del tipo contenedor, como se muestra en el ejemplo siguiente: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 {/*...*/}
}

En el ejemplo anterior, T es una restricción de tipo en el contexto del método Add, y un parámetro de tipo sin enlazar en el contexto de la clase 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.

Los parámetros de tipo también pueden usarse como restricciones en definiciones de clase genéricas.Type parameters can also be used as constraints in generic class definitions. El parámetro de tipo debe declararse dentro de los corchetes angulares junto con los demás 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 { }

La utilidad de los parámetros de tipo como restricciones con clases genéricas es limitada, ya que el compilador no puede dar por supuesto nada sobre el parámetro de tipo, excepto que 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 restricciones en clases genéricas en escenarios en los que quiere aplicar una relación de herencia entre dos 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.

Restricción no administradaUnmanaged constraint

A partir de C# 7.3, puede usar la restricción unmanaged para especificar que el parámetro de tipo debe ser un tipo no administrado.Beginning with C# 7.3, you can use the unmanaged constraint to specify that the type parameter must be an unmanaged type. Un tipo no administrado es un tipo que no es un tipo de referencia y no contiene campos de tipo de referencia en ningún nivel de anidamiento.An unmanaged type is a type that is not a reference type and doesn't contain reference type fields at any level of nesting. La restricción unmanaged permite escribir rutinas reutilizables para trabajar con tipos que se pueden manipular como bloques de memoria, como se muestra en el ejemplo siguiente: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;
}

El método anterior debe compilarse en un contexto unsafe, ya que usa el operador sizeof en un tipo que se desconoce si es integrado.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. Sin la restricción unmanaged, el operador sizeof no está disponible.Without the unmanaged constraint, the sizeof operator is unavailable.

Restricciones de delegadoDelegate constraints

También a partir de C# 7.3, puede usar System.Delegate o System.MulticastDelegate como una restricción de clase base.Also beginning with C# 7.3, you can use System.Delegate or System.MulticastDelegate as a base class constraint. CLR siempre permitía esta restricción, pero el lenguaje C# no la permitía.The CLR always allowed this constraint, but the C# language disallowed it. La restricción System.Delegate permite escribir código que funciona con los delegados en un modo con seguridad de tipos.The System.Delegate constraint enables you to write code that works with delegates in a type-safe manner. En el código siguiente se define un método de extensión que combina dos delegados siempre y cuando sean del mismo 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;

Puede usar el método anterior para combinar delegados que sean del mismo 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);

Si quita la marca de comentario de la última línea, no se compilará.If you uncomment the last line, it won't compile. Tanto first como test son tipos de delegado, pero son tipos de delegado distintos.Both first and test are delegate types, but they are different delegate types.

Restricciones de enumeraciónEnum constraints

A partir de C# 7.3, también puede especificar el tipo System.Enum como una restricción de clase base.Beginning in C# 7.3, you can also specify the System.Enum type as a base class constraint. CLR siempre permitía esta restricción, pero el lenguaje C# no la permitía.The CLR always allowed this constraint, but the C# language disallowed it. Los genéricos que usan System.Enum proporcionan programación con seguridad de tipos para almacenar en caché los resultados de usar los métodos estáticos en System.Enum.Generics using System.Enum provide type-safe programming to cache results from using the static methods in System.Enum. En el ejemplo siguiente se buscan todos los valores válidos para un tipo de enumeración y, después, se compila un diccionario que asigna esos valores a su representación de cadena.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;
}

Los métodos empleados usan reflexión, lo que tiene consecuencias en el rendimiento.The methods used make use of reflection, which has performance implications. Puede llamar a este método para compilar una recopilación que se almacene en caché y se vuelva a usar, en lugar de repetir las llamadas que requieren reflexión.You can call this method to build a collection that is cached and reused rather than repeating the calls that require reflection.

Podría usarla como se muestra en el ejemplo siguiente para crear una enumeración y compilar un diccionario con sus nombres y 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}");

Vea tambiénSee Also