Partager via


Variance dans les délégués (C#)

.NET Framework 3.5 a introduit le support de la variance qui permet d'associer les signatures de méthodes aux types de délégués pour tous les délégués en C#. Cela signifie que vous pouvez affecter à des délégués non seulement des méthodes qui ont des signatures correspondantes, mais également des méthodes qui retournent des types dérivés (covariance) ou qui acceptent des paramètres qui ont moins de types dérivés (contravariance) que ceux spécifiés par le type délégué. Cela inclut les délégués génériques et non génériques.

Par exemple, considérez le code suivant, qui a deux classes et deux délégués : générique et non générique.

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

Lorsque vous créez des délégués de types SampleDelegate ou SampleGenericDelegate<A, R>, vous pouvez affecter l’une des méthodes suivantes à ces délégués.

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

L’exemple de code suivant illustre la conversion implicite entre la signature de méthode et le type délégué.

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

Pour plus d’exemples, consultez Utilisation de la variance dans les délégués (C#) et Utilisation de la variance pour les délégués génériques Func et Action (C#).

Variance dans les paramètres de type générique

Dans .NET Framework 4 ou version ultérieure, vous pouvez activer la conversion implicite entre les délégués, afin que les délégués génériques qui ont différents types spécifiés par les paramètres de type générique puissent être attribués les uns aux autres, si les types sont hérités les uns des autres, comme requis par la variance.

Pour activer la conversion implicite, vous devez déclarer explicitement des paramètres génériques dans un délégué en tant que covariant ou contravariant à l'aide du mot clé in ou out.

L’exemple de code suivant montre comment créer un délégué qui a un paramètre de type générique covariant.

// 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 vous utilisez uniquement la prise en charge de la variance pour faire correspondre les signatures de méthode avec des types délégués et que vous n'utilisez pas les mots clés in et out, vous pouvez parfois instancier des délégués avec des expressions lambda ou des méthodes identiques, mais vous ne pouvez pas affecter un délégué à un autre.

Dans l’exemple de code suivant, SampleGenericDelegate<String> ne peut pas être converti explicitement en SampleGenericDelegate<Object>, bien qu’il String hérite Object. Vous pouvez résoudre ce problème en marquant le paramètre T générique avec le out mot clé.

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

Délégués génériques qui ont des paramètres de type variant dans .NET

.NET Framework 4 a introduit la prise en charge des variances pour les paramètres de type générique dans plusieurs délégués génériques existants :

Pour plus d’informations et d’exemples, consultez Utilisation de la variance pour les délégués génériques Func et Action (C#).

Déclaration de paramètres de type variant dans les délégués génériques

Si un délégué générique a des paramètres de type générique covariant ou contravariant, il peut être appelé délégué générique variant.

Vous pouvez déclarer un paramètre de type générique covariant dans un délégué générique à l’aide du out mot clé. Le type covariant ne peut être utilisé que comme type de retour de méthode et non comme type d’arguments de méthode. L’exemple de code suivant montre comment déclarer un délégué générique covariant.

public delegate R DCovariant<out R>();  

Vous pouvez déclarer un paramètre de type générique contravariant dans un délégué générique à l’aide du in mot clé. Le type contravariant peut être utilisé uniquement comme type d’arguments de méthode et non comme type de retour de méthode. L’exemple de code suivant montre comment déclarer un délégué générique contravariant.

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

Importante

ref, inet out les paramètres en C# ne peuvent pas être marqués comme variant.

Il est également possible de prendre en charge la variance et la covariance dans le même délégué, mais pour différents paramètres de type. Ceci est illustré dans l’exemple suivant.

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

Instanciation et appel de délégués génériques variants

Vous pouvez instancier et appeler des délégués variant tout comme vous instanciez et appelez des délégués invariants. Dans l’exemple suivant, le délégué est instancié par une expression lambda.

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

Combinaison des délégués génériques variants

Ne combinez pas de délégués variables. La méthode Combine ne prend pas en charge la conversion des délégués variants et nécessite le même type pour tous les délégués. Cela peut entraîner une exception d’exécution lorsque vous combinez des délégués à l’aide de la Combine méthode ou à l’aide de l’opérateur + , comme illustré dans l’exemple de code suivant.

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

Variance dans les paramètres de type générique pour les types valeur et référence

La variance pour les paramètres de type générique est prise en charge uniquement pour les types de référence. Par exemple, DVariant<int> ne peut pas être converti implicitement en DVariant<Object> ou DVariant<long>, car l’entier est un type valeur.

L’exemple suivant montre que la variance dans les paramètres de type générique n’est pas prise en charge pour les types valeur.

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

Voir aussi