Condividi tramite


Varianza nei delegati (C#)

.NET Framework 3.5 ha introdotto il supporto della varianza per abbinare le firme dei metodi ai tipi delegati in tutti i delegate C#. Ciò significa che è possibile assegnare a delegati non solo metodi con firme corrispondenti, ma anche metodi che restituiscono più tipi derivati (covarianza) o che accettano parametri con tipi meno derivati (controvarianza) rispetto a quelli specificati dal tipo delegato. Sono inclusi sia delegati generici che non generici.

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

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 SampleDelegate tipi o SampleGenericDelegate<A, R> , è possibile assegnare uno dei metodi seguenti a tali delegati.

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

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

// 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 altri esempi, vedere Uso della varianza nei delegati (C#) e Uso della varianza per i delegati generici Func e Action (C#).

Varianza nei parametri di tipo generico

In .NET Framework 4 o versioni successive è possibile abilitare la conversione implicita tra delegati, in modo che i delegati generici con tipi diversi specificati dai parametri di tipo generico possano essere assegnati tra loro, se i tipi vengono ereditati tra loro come richiesto dalla varianza.

Per abilitare la conversione implicita, è necessario dichiarare in modo esplicito i parametri generici in un delegato come covariante o controvariante usando la in parola chiave o out .

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

Se si usa solo il supporto della varianza per associare le firme dei metodi ai tipi delegati e non si usano le in parole chiave e out , talvolta è possibile creare istanze di delegati con espressioni o metodi lambda identici, ma non è possibile assegnare un delegato a un altro.

Nell'esempio di codice seguente non SampleGenericDelegate<String> può essere convertito in modo esplicito in SampleGenericDelegate<Object>, anche se String eredita Object. È possibile risolvere questo problema contrassegnando il parametro generico T con la out parola chiave .

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 varianti in .NET

.NET Framework 4 ha introdotto il supporto della varianza per i parametri di tipo generico in diversi delegati generici esistenti:

Per ulteriori informazioni ed esempi, vedere Utilizzare la varianza per i delegati generici Func e Action (C#).

Dichiarazione dei parametri di tipo variante nei delegati generici

Se un delegato generico ha parametri di tipo generico covariante o controvarianti, può essere definito delegato generico variante.

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

public delegate R DCovariant<out R>();  

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

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

Importante

refI parametri , ine out in C# non possono essere contrassegnati come varianti.

È anche possibile supportare la varianza e la covarianza nello stesso delegato, ma per parametri di tipo diversi. Questo è illustrato nell'esempio seguente.

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

Creazione di istanze e invocazione di delegati generici varianti

È possibile creare un'istanza e richiamare delegati varianti esattamente come si crea un'istanza e si richiamano delegati invarianti. Nell'esempio seguente, il delegato è istanziato da un'espressione lambda.

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

Combinazione di delegati generici varianti

Non combinare delegati varianti. Il Combine metodo non supporta la conversione di delegati varianti e si aspetta che i delegati siano esattamente dello stesso tipo. Ciò può causare un'eccezione in fase di esecuzione quando si combinano delegati usando il Combine metodo o usando l'operatore , come illustrato nell'esempio + di codice seguente.

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

La varianza per i parametri di tipo generico è supportata solo per i tipi di riferimento. Ad esempio, DVariant<int> non può essere convertito in modo implicito in DVariant<Object> o DVariant<long>, perché integer è un tipo di valore.

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

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

Vedere anche