C# 4.0 introduit une nouveauté très intéressante au niveau des déclarations d’interfaces / délégués génériques : la notion de Co-variance et Contra-variance.
La co-variance permet de dire que si un type implémente IMonInterface<TypeDérivé>, il implémente aussi IMonInterface<TypeParent>.
Pour la contra-variance, c’est exactement le contraire.
Du coup, les développeurs de la BCL ont modifié la déclaration de pas mal d’interfaces et délégués génériques pour profiter de ces règles : ainsi tout IEnumerable<string> implémente automatiquement IEnumerable<object> et tout délégué de type Action<object> peut être utilisé à la place d’un Action<string>.
En fait cette notion de co-variance / contra-variance, bien que non déclarable en C# et VB dans leur versions actuelle est présente dans les spécifications du langage MSIL depuis .Net 2.0, et si l’on en a le courage, on peut tout a fait créer des interfaces / délégués co/contra-variants. Pour ceci, il faut simplement indiquer un “+” (co-variant) ou un “-“ (contra-variant) devant le paramètre du type. Voici un exemple avec 2 interfaces définie en MSIL :
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
.ver 2:0:0:0
}
.assembly Cotypes
{
.ver 0:0:0:0
}
.module Cotypes.dll
// +T indique que l'interface est CoVariante (toute implementation de IValueProvider<ChildClass>
// implemente IValueProvider<ParentClass>)
.class interface public abstract auto ansi Cotypes.IValueProvider`1<+T>
{
.method public hidebysig newslot abstract virtual
instance !T GetValue() cil managed
{
}
}
// -T indique que l'interface est ContraVariante (toute implementation de IValueConsumer<ParentClass>
// implemente IValueConsumer<ChildClass>)
.class interface public abstract auto ansi Cotypes.IValueConsumer`1<-T>
{
.method public hidebysig newslot abstract virtual
instance void SetValue(!T 'value') cil managed
{
}
}
Une fois compilée sous forme de DLL, on peut alors implémenter ces interfaces en C# et profiter la co/contra-variance :
static void Main(string[] args)
{
IValueProvider<string> stringProvider = new StringValueProvider("HelloWorld");
IValueProvider<object> objProvider = stringProvider as IValueProvider<object>;
IValueConsumer<object> objConsumer = new ConsoleDisplayer();
IValueConsumer<string> stringConsumer = objConsumer as IValueConsumer<string>;
objConsumer.SetValue(objProvider.GetValue());
stringConsumer.SetValue(stringProvider.GetValue());
}
En C# 4.0, la seule différence (en dehors du fait que l’écriture de telles interfaces peut se faire directement en C#) est qu’il n’y a pas besoin du mot-clef as, l’opération de cast se fait implicitement.
Vous pouvez télécharger les sources de cet exemple ici.