Compartir a través de


Varianza en delegados (C#)

En .NET Framework 3.5 se presentó por primera vez la compatibilidad con la varianza para hacer coincidir firmas de método con tipos de delegados en todos los delegados en C#. Esto significa que puede asignar a delegados no solo métodos que tienen firmas coincidentes, sino también métodos que devuelven más tipos derivados (covarianza) o que aceptan parámetros que tienen tipos menos derivados (contravariancia) que los especificados por el tipo delegado. Esto incluye delegados genéricos y no genéricos.

Por ejemplo, considere el código siguiente, que tiene dos clases y dos delegados: genéricos y no genéricos.

public class First { }  
public class Second : First { }  
public delegate First SampleDelegate(Second a);  
public delegate R SampleGenericDelegate<A, R>(A a);  

Al crear delegados de los tipos SampleDelegate o SampleGenericDelegate<A, R>, puede asignar cualquiera de los métodos siguientes a esos delegados.

// Matching signature.  
public static First ASecondRFirst(Second second)  
{ return new First(); }  
  
// The return type is more derived.  
public static Second ASecondRSecond(Second second)  
{ return new Second(); }  
  
// The argument type is less derived.  
public static First AFirstRFirst(First first)  
{ return new First(); }  
  
// The return type is more derived
// and the argument type is less derived.  
public static Second AFirstRSecond(First first)  
{ return new Second(); }  

En el ejemplo de código siguiente se muestra la conversión implícita entre la firma del método y el tipo de delegado.

// Assigning a method with a matching signature
// to a non-generic delegate. No conversion is necessary.  
SampleDelegate dNonGeneric = ASecondRFirst;  
// Assigning a method with a more derived return type
// and less derived argument type to a non-generic delegate.  
// The implicit conversion is used.  
SampleDelegate dNonGenericConversion = AFirstRSecond;  
  
// Assigning a method with a matching signature to a generic delegate.  
// No conversion is necessary.  
SampleGenericDelegate<Second, First> dGeneric = ASecondRFirst;  
// Assigning a method with a more derived return type
// and less derived argument type to a generic delegate.  
// The implicit conversion is used.  
SampleGenericDelegate<Second, First> dGenericConversion = AFirstRSecond;  

Para obtener más ejemplos, vea Usar varianza en delegados (C#) y Usar la varianza para los delegados genéricos Func y Action (C#).

Varianza en parámetros de tipo genérico

En .NET Framework 4 o posterior puede habilitar la conversión implícita entre los delegados, de modo que los delegados genéricos con tipos diferentes especificados por parámetros de tipo genérico se puedan asignar entre sí, en el caso de que los tipos se hereden entre sí, como requiere la varianza.

Para habilitar la conversión implícita, debe declarar explícitamente parámetros genéricos en un delegado como covariante o contravariante mediante la in palabra clave o out .

En el ejemplo de código siguiente se muestra cómo puede crear un delegado que tenga un parámetro de tipo genérico covariante.

// Type T is declared covariant by using the out keyword.  
public delegate T SampleGenericDelegate <out T>();  
  
public static void Test()  
{  
    SampleGenericDelegate <String> dString = () => " ";  
  
    // You can assign delegates to each other,  
    // because the type T is declared covariant.  
    SampleGenericDelegate <Object> dObject = dString;
}  

Si solo utiliza la compatibilidad de varianza para hacer coincidir las firmas de método con los tipos de delegados y no utiliza las palabras clave in y out, es posible que a veces pueda instanciar delegados con expresiones lambda o métodos idénticos, pero no pueda asignar un delegado a otro.

En el ejemplo de código siguiente, SampleGenericDelegate<String> no se puede convertir explícitamente en SampleGenericDelegate<Object>, aunque String hereda Object. Puede corregir este problema marcando el parámetro T genérico con la out palabra clave .

public delegate T SampleGenericDelegate<T>();  
  
public static void Test()  
{  
    SampleGenericDelegate<String> dString = () => " ";  
  
    // You can assign the dObject delegate  
    // to the same lambda expression as dString delegate  
    // because of the variance support for
    // matching method signatures with delegate types.  
    SampleGenericDelegate<Object> dObject = () => " ";  
  
    // The following statement generates a compiler error  
    // because the generic type T is not marked as covariant.  
    // SampleGenericDelegate <Object> dObject = dString;  
  
}  

Delegados genéricos con parámetros de tipo variante en .NET

.NET Framework 4 introdujo compatibilidad de varianza con parámetros de tipo genérico en varios delegados genéricos existentes:

Para obtener más información y ejemplos, vea Using Variance for Func and Action Generic Delegates (C#) (Usar varianza para delegados genéricos de func y acción [C#]).

Declarar parámetros de tipo variante en delegados genéricos

Si un delegado genérico tiene parámetros de tipo genérico covariantes o contravariantes, se puede denominar delegado genérico variante.

Puede declarar un parámetro de tipo genérico covariante en un delegado genérico mediante la out palabra clave . El tipo covariante solo se puede usar como un tipo de retorno de método y no como un tipo de los argumentos de método. En el ejemplo de código siguiente se muestra cómo declarar un delegado genérico covariante.

public delegate R DCovariant<out R>();  

Puede declarar un parámetro de tipo genérico como contravarianza en un delegado genérico mediante la palabra clave in. El tipo contravariante solo se puede usar como tipo de argumentos de método y no como tipo de retorno de método. En el ejemplo de código siguiente se muestra cómo declarar un delegado genérico contravariante.

public delegate void DContravariant<in A>(A a);  

Importante

refLos parámetros , iny out en C# no se pueden marcar como variantes.

También es posible admitir la varianza y la covarianza en el mismo delegado, pero para parámetros de tipo diferentes. Esto se muestra en el ejemplo siguiente.

public delegate R DVariant<in A, out R>(A a);  

Crear instancias de delegados genéricos variantes e invocarlos

Puede crear instancias e invocar delegados variantes así como crear instancias e invocar delegados invariables. En el ejemplo siguiente, se crea una instancia del delegado mediante una expresión lambda.

DVariant<String, String> dvariant = (String str) => str + " ";  
dvariant("test");  

Combinar delegados genéricos variantes

No combine delegados variantes. El método Combine no admite la conversión de delegados de tipo variante y espera que los delegados sean exactamente del mismo tipo. Esto puede provocar una excepción en tiempo de ejecución al combinar delegados mediante el Combine método o mediante el + operador , como se muestra en el ejemplo de código siguiente.

Action<object> actObj = x => Console.WriteLine("object: {0}", x);  
Action<string> actStr = x => Console.WriteLine("string: {0}", x);  
// All of the following statements throw exceptions at run time.  
// Action<string> actCombine = actStr + actObj;  
// actStr += actObj;  
// Delegate.Combine(actStr, actObj);  

Varianza en parámetros de tipo genérico para tipos de valor y referencia

La varianza de los parámetros de tipo genérico solo se admite para los tipos de referencia. Por ejemplo, DVariant<int> no se puede convertir implícitamente en DVariant<Object> o DVariant<long>, porque integer es un tipo de valor.

En el ejemplo siguiente se muestra que la varianza en los parámetros de tipo genérico no se admite para los tipos de valor.

// The type T is covariant.  
public delegate T DVariant<out T>();  
  
// The type T is invariant.  
public delegate T DInvariant<T>();  
  
public static void Test()  
{  
    int i = 0;  
    DInvariant<int> dInt = () => i;  
    DVariant<int> dVariantInt = () => i;  
  
    // All of the following statements generate a compiler error  
    // because type variance in generic parameters is not supported  
    // for value types, even if generic type parameters are declared variant.  
    // DInvariant<Object> dObject = dInt;  
    // DInvariant<long> dLong = dInt;  
    // DVariant<Object> dVariantObject = dVariantInt;  
    // DVariant<long> dVariantLong = dVariantInt;
}  

Consulte también