Compartir a través de


Varianza en delegados (C# y Visual Basic)

.NET Framework 3.5 y Visual Studio 2008 introdujeron la compatibilidad con la varianza para hacer coincidir las firmas de método con los tipos de delegado en todos los delegados de C# y Visual Basic. Esto significa que se puede asignar a los delegados no solo los métodos con firmas coincidentes, sino también los métodos que devuelven tipos más derivados (covarianza) o que aceptan parámetros que tienen tipos menos derivados (contravarianza) respecto al especificado por el tipo de delegado. Esto incluye tanto a los delegados genéricos como 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
End Class

Public Class Second
    Inherits First
End Class

Public Delegate Function SampleDelegate(ByVal a As Second) As First
Public Delegate Function SampleGenericDelegate(Of A, R)(ByVal a As A) As R
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> (SampleDelegate(Of A, R) en Visual Basic), puede asignar cualquiera de los siguientes métodos a dichos delegados.

' Matching signature.
Public Shared Function ASecondRFirst(
    ByVal second As Second) As First
    Return New First()
End Function

' The return type is more derived.
Public Shared Function ASecondRSecond(
    ByVal second As Second) As Second
    Return New Second()
End Function

' The argument type is less derived.
Public Shared Function AFirstRFirst(
    ByVal first As First) As First
    Return New First()
End Function

' The return type is more derived 
' and the argument type is less derived.
Public Shared Function AFirstRSecond(
    ByVal first As First) As Second
    Return New Second()
End Function
// Matching signature.
public static First ASecondRFirst(Second first)
{ 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 de método y el tipo de delegado.

' Assigning a method with a matching signature 
' to a non-generic delegate. No conversion is necessary.
Dim dNonGeneric As SampleDelegate = AddressOf 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.
Dim dNonGenericConversion As SampleDelegate = AddressOf AFirstRSecond

' Assigning a method with a matching signature to a generic delegate.
' No conversion is necessary.
Dim dGeneric As SampleGenericDelegate(Of Second, First) = AddressOf ASecondRFirst
' Assigning a method with a more derived return type 
' and less derived argument type to a generic delegate.
' The implicit conversion is used.
Dim dGenericConversion As SampleGenericDelegate(Of Second, First) = AddressOf AFirstRSecond
// 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 Utilizar varianza en delegados (C# y Visual Basic) y Usar la varianza para los delegados genéricos Func y Action (C# y Visual Basic).

Varianza en parámetros de tipo genérico

En .NET Framework 4, se puede habilitar la conversión implícita entre los delegados, de forma que los delegados genéricos cuyos parámetros de tipo genérico especifican tipos distintos pueden asignarse entre sí, siempre que los tipos se hereden unos de otros tal y como requiere la varianza.

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

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

' Type T is declared covariant by using the out keyword.
Public Delegate Function SampleGenericDelegate(Of Out T)() As T
Sub Test()
    Dim dString As SampleGenericDelegate(Of String) = Function() " "
    ' You can assign delegates to each other,
    ' because the type T is declared covariant.
    Dim dObject As SampleGenericDelegate(Of Object) = dString
End Sub
// 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 solamente usa la compatibilidad con la varianza para hacer coincidir las firmas de método con los tipos de delegado y no usa las palabras clave in y out, es posible que en algunas ocasiones pueda crear instancias de los delegados con métodos o expresiones lambda 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> (SampleGenericDelegate(Of String) en SampleGenericDelegate(Of Object) en Visual Basic), aunque el objeto String hereda el objeto Object. Para solucionar este problema, marque el parámetro genérico T con la palabra clave out.

Public Delegate Function SampleGenericDelegate(Of T)() As T
Sub Test()
    Dim dString As SampleGenericDelegate(Of String) = Function() " "

    ' 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.
    Dim dObject As SampleGenericDelegate(Of Object) = Function() " "

    ' The following statement generates a compiler error
    ' because the generic type T is not marked as covariant.
    ' Dim dObject As SampleGenericDelegate(Of Object) = dString


End Sub
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 Framework

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

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

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

Si un delegado genérico tiene parámetros de tipo genérico covariante o contravariante, este puede denominarse delegado genérico variante.

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

Public Delegate Function DCovariant(Of Out R)() As R
public delegate R DCovariant<out R>();

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

Public Delegate Sub DContravariant(Of In A)(ByVal a As A)
public delegate void DContravariant<in A>(A a);
Nota importanteImportante

El parámetro ByRef de Visual Basic y los parámetros ref y out de C# no se pueden marcar como variante.

También es posible admitir la varianza y la covarianza en el mismo delegado, pero para distintos parámetros de tipo. El ejemplo siguiente muestra esta opción.

Public Delegate Function DVariant(Of In A, Out R)(ByVal a As A) As R
public delegate R DVariant<in A, out R>(A a);

Crear instancias de delegados genéricos variantes e invocarlos

Puede crear instancias de los delegados variantes e invocarlos de la misma forma que se crean instancias de los delegados invariables y se les invoca. En el ejemplo siguiente se crea una instancia del delegado mediante una expresión lambda.

Dim dvariant As DVariant(Of String, String) = Function(str) str + " "
dvariant("test")
DVariant<String, String> dvariant = (String str) => str + " ";
dvariant("test");

Combinar delegados genéricos variantes

No debe combinar delegados variantes. El método Combine no admite la conversión de delegados variantes y espera que los delegados sean exactamente del mismo tipo. Esto puede provocar una excepción en tiempo de ejecución cuando los delegados se combinan mediante el método Combine (en C# y Visual Basic) o mediante el operador + (en C#), como se muestra en el ejemplo de código siguiente.

Dim actObj As Action(Of Object) = Sub(x) Console.WriteLine("object: {0}", x)
Dim actStr As Action(Of String) = Sub(x) Console.WriteLine("string: {0}", x)

' The following statement throws an exception at run time.
' Dim actCombine = [Delegate].Combine(actStr, actObj)
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 los tipos de referencia y valor

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

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

' The type T is covariant.
Public Delegate Function DVariant(Of Out T)() As T
' The type T is invariant.
Public Delegate Function DInvariant(Of T)() As T
Sub Test()
    Dim i As Integer = 0
    Dim dInt As DInvariant(Of Integer) = Function() i
    Dim dVaraintInt As DVariant(Of Integer) = Function() 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.
    ' Dim dObject As DInvariant(Of Object) = dInt
    ' Dim dLong As DInvariant(Of Long) = dInt
    ' Dim dVaraintObject As DInvariant(Of Object) = dInt
    ' Dim dVaraintLong As DInvariant(Of Long) = dInt
End Sub
// 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;            
}

Conversión de delegado flexible en Visual Basic

La conversión de delegado flexible, incluida en Visual Basic 2008, permite una mayor flexibilidad a la hora de hacer coincidir las firmas de método con los tipos de delegado. Por ejemplo, permite omitir las especificaciones de los parámetros y los valores devueltos de una función al asignar un método a un delegado. Para obtener más información, vea Conversión de delegado no estricta (Visual Basic).

Vea también

Tareas

Cómo: Combinar delegados (delegados de multidifusión) (Guía de programación de C#)

Referencia

Usar la varianza para los delegados genéricos Func y Action (C# y Visual Basic)

Conceptos

Genéricos en .NET Framework