Varianza nei delegati (C# e Visual Basic)

In .NET Framework 3.5 e in Visual Studio 2008 è stato introdotto il supporto della varianza per la corrispondenza di firme di metodo con i tipi delegati in tutti i delegati in C# e Visual Basic. È pertanto possibile assegnare ai delegati non solo metodi con firme corrispondenti, ma anche metodi che restituiscono tipi più derivati (covarianza) o che accettano parametri con tipi meno derivati (controvarianza) di quello specificato dal tipo delegato, inclusi i delegati generici e non generici.

Si consideri, ad esempio, il codice seguente che dispone di due classi e di due delegati: generico e non generico.

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);

Quando si creano delegati dei tipi SampleDelegate o SampleGenericDelegate<A, R> (SampleDelegate(Of A, R) in Visual Basic), è possibile assegnare uno dei metodi seguenti a tali delegati.

' 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(); }

Nell'esempio di codice seguente viene illustrata la conversione implicita tra la firma di metodo e il tipo delegato.

' 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;

Per ulteriori esempi, vedere Utilizzo della varianza nei delegati (C# e Visual Basic) e Utilizzo della varianza per i delegati generici Func e Action (C# e Visual Basic).

Varianza nei parametri di tipo generico

In .NET Framework 4, è possibile abilitare la conversione implicita tra delegati, in modo che i delegati generici con tipi diversi specificati da parametri di tipo generico possano essere assegnato l'uno all'altro, se i tipi vengono ereditati l'uno dall'altro come richiesto dalla varianza.

Per abilitare la conversione implicita, è necessario dichiarare in modo esplicito i parametri generici in un delegato come covarianti o controvarianti utilizzando la parola chiave in o out.

Nell'esempio di codice seguente viene illustrato come sia possibile creare un delegato che dispone di un parametro di tipo generico 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;           
}

Se si utilizza solo il supporto della varianza per abbinare le firme di metodo ai tipi delegati e non si utilizzano le parole chiave in e out, è possibile che talvolta vengano create istanze di delegati con espressioni lambda o metodi identici, ma non è possibile assegnare un delegato a un altro.

Nell'esempio di codice seguente, non è possibile convertire SampleGenericDelegate<String> in modo esplicito in SampleGenericDelegate<Object> (SampleGenericDelegate(Of String) in SampleGenericDelegate(Of Object) in Visual Basic), anche se String eredita Object. È possibile risolvere questo problema contrassegnando il parametro generico T con la parola chiave 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;



}

Delegati generici con parametri di tipo variant in .NET Framework

In .NET Framework 4 viene introdotto il supporto della varianza per parametri di tipo generico in diversi delegati generici esistenti.

Per ulteriori informazioni ed esempi, vedere Utilizzo della varianza per i delegati generici Func e Action (C# e Visual Basic).

Dichiarazione di parametri di tipo variant in delegati generici

Se un delegato generico dispone di parametri di tipo generico covariante o controvariante, è possibile definirlo come delegato generico variant.

È possibile dichiarare una covariante del parametro di tipo generico in un delegato generico utilizzando la parola chiave out. Il tipo covariante può essere utilizzato solo come tipo restituito del metodo e non come tipo di argomenti del metodo. Nell'esempio di codice seguente viene illustrato come dichiarare un delegato generico covariante.

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

È possibile dichiarare una controvariante del parametro di tipo generico in un delegato generico utilizzando la parola chiave in. Il tipo controvariante può essere utilizzato solo come tipo di argomenti del metodo e non come tipo restituito del metodo. Nell'esempio di codice seguente viene illustrato come dichiarare un delegato generico controvariante.

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

I parametri ByRef in Visual Basic e i parametri ref e out in C# non possono essere contrassegnati come variant.

È inoltre possibile supportare sia la varianza che la covarianza nello stesso delegato, ma per parametri di tipo diverso, come illustrato nell'esempio seguente.

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);

Creazione di istanze e richiamo di delegati generici variant

È possibile creare istanze e richiamare delegati variant mentre si creano istanze e si richiamano delegati invariabili. Nell'esempio seguente, l'istanza del delegato viene creata da un'espressione lambda.

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

Combinazione dei delegati generici varianti

I delegati varianti non devono essere combinati. Il metodo Combine non supporta la conversione dei delegati varianti e prevede che i delegati siano esattamente dello stesso tipo. Ciò può portare a un'eccezione in fase di esecuzione se si combinano delegati utilizzando il metodo Combine (in C# e Visual Basic) o l'operatore + (in C#), come mostrato nel seguente esempio di codice.

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 in parametri di tipo generico per tipi di valore e di riferimento

La varianza per i parametri di tipo generico è supportata solo per i tipi di riferimento. Non è possibile, ad esempio, convertire in modo implicito DVariant<int> (DVariant(Of Int) in Visual Basic) in DVariant<Object> o DVaraint<long> (DVariant(Of Object) o DVaraint(Of Long) in Visual Basic), perché il valore intero è un tipo di valore.

Nell'esempio seguente viene dimostrato che la varianza nei parametri di tipo generico non è supportata per i tipi di valore.

' 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;            
}

Conversione di tipo relaxed di delegati in Visual Basic

La conversione di tipo relaxed dei delegati, introdotta in Visual Basic 2008, consente una maggiore flessibilità nella corrispondenza di firme di metodo con i tipi delegati. Consente, ad esempio, di omettere le specifiche dei parametri e i valori restituiti delle funzioni quando si assegna un metodo a un delegato. Per ulteriori informazioni, vedere Relaxed Delegate Conversion.

Vedere anche

Attività

Procedura: combinare delegati multicast (Guida per programmatori C#)

Riferimenti

Utilizzo della varianza per i delegati generici Func e Action (C# e Visual Basic)

Concetti

Generics in .NET Framework