Astuces DotNet (Sébastien Courtois)

14/04/2010

[Nouveautés C# .NET 4] Introduction à la Covariance / Contravariance

Filed under: .NET, Débutant, Intermediaire — Étiquettes : , , , , , — sebastiencourtois @ 14:44

Pour la sortie de .NET 4, je vais faire quelques articles sur les nouveautés du langages (en attendant de pouvoir jouer avec SL4). Aujourd’hui, nous allons voir la notion de Covariance / Contravariance.

EDIT (08/05/2010) : La Covariance/Contravariance n’est pas disponible pour Silverlight 4

  • Définitions

La covariance et la contravariance dixit Wikipedia :

Within the type system of a programming language, a typing rule or a type conversion operator is:

  • covariant if it preserves the ordering, ≤, of types, which orders types from more specific to more generic;
  • contravariant if it reverses this ordering, which orders types from more generic to more specific;
  • invariant if neither of these apply.

Bon, je suis d’accord qu’avec cette définition, on est pas plus avancé. On voit que cela parle de conversion de type et de généricité mais ça reste très abstrait.

Nous allons donc voir des exemples puis revenir sur ces définitions.

  • Exemples sur la covariance 

Nous prendrons une architecture simple pour ces exemples :

public abstract class Person { }
public class Employe : Person { }
public class Customer : Person { }
public class Manager : Employe { }

Nous allons maintenant jouer avec des tableaux de ces objets :

Person[] MaSociete = new Manager[10];  // .NET 3.5 / 4 : OK 
Employe[] MaSociete2 = new Manager[10]; // .NET 3.5 / 4 : OK 

Rien de transcendant là dessus. On a un tableau de Manager. Un manager étant une personne (ça dépend dans quel société …:)), il n’y a pas de problème à mettre les données d’un tableau de manager dans un tableau de personnes. De même pour mettre nos données manager dans le tableau d’employés (Manager étant un employé).

Faison la même chose avec des listes

Manager[] MaSociete3 = new Manager[10];
IEnumerable<Manager> im = MaSociete3.Where(p => p.ToString() == "Manager"); // .NET 3.5 / 4 : OK 
IEnumerable<Manager> im2 = new List<Manager>(); // .NET 3.5 / 4 : OK 

Le requête LINQ renvoie un IEnumerable<Manager> que je peux donc mettre dans im. De même pour une liste de manager car List<T> implémente IEnumerable<T>.

IEnumerable<Person> ip = im;  // .NET 3.5 : Erreur de compilation  / .NET 4 : OK

Sur cette ligne, on obtient une erreur de compilation en .NET 3.5 (Cannot Convert IEnumerable<Manager> to IEnumerable<Person>). Pourtant cela devrait marcher comme un tableau (un manager est une personne donc un tableau de personne peut contenir des données venant d’un tableau de manager). Cela n’est pas possible car, en .NET 3.5, IEnumerable<T> n’est pas covariant (à l’inverse de Array qui lui est covariant). C’est pour corriger ce manque que Microsoft a autorisé la covariance des interfaces et des délégués en .NET 4.

En .NET 4, je peux ainsi écrire :

IEnumerable<Person> ipl = new List<Manager>();  // .NET 3.5 : Erreur de compilation  / .NET 4 : OK
Func<string> funcString = () => { return "Bonjour le monde"; };
Func<object> funcObject = funcString;         // .NET 3.5 : Erreur de compilation  / .NET 4 : OK

Pour résumer, on peut dire que la covariance permet de caster un type générique A<T> dans un type générique A<K> si T hérite directement ou indirectement de K et si le type T est un paramètre de sortie (nous reviendrons sur cette notion plus loin).

Remarque :  La covariance (ainsi que la contravariance) fonctionne uniquement avec des interfaces et des délégués. Les arguments génériques doivent être des types références. Il n’est pas possible de faire les lignes suivantes :

List<Person> lo = new List<Manager>();         // .NET 3.5 / 4 : Erreur de compilation 
IEnumerable<ValueType> lo = new List<int>();    // .NET 3.5 / 4 : Erreur de compilation 

  • Exemples sur la contravariance 

Dans certains cas, on aimerait que l’inverse soit possible.

  Action<object> actObject = (object o) => { };
  Action<string> actString = actObject;          // .NET 3.5 : Erreur de compilation  / .NET 4 : OK

Dans cet exemple, je souhaite pouvoir caster mon délégués avec une valeur de retour de type string ou un de ses parents. En effet,lorsque je récupère une donnée, je peux la caster dans une variable d’un type dont elle hérite. C’est le principe de la contravariance introduite dans .NET 4.

La contravariance est la possiblité de caster un type générique A<T> dans un type générique A<K> si T est parent direct ou indirect de K et si T est un paramètre d’entrée (nous reviendrons sur cette notion plus loin).

  • Les interfaces / Délégués déja covariant et contravariant

Voici la liste des interfaces/délégués ayant été mis à jour au sein du framework .net 4 pour tenir compte de cette nouvelle possibilité.

 

  • Exemple pratique : La création d’une interface covariant et contravariant

Prenons l’exemple d’une classe réalisant de la transformations de données.

public interface IDataTransformer<T, K>
{
    K Transform(T data);
}

public class DataTransformer<T, K> : IDataTransformer<T, K>
{
    public K Transform(T data)
    {
        return default(K);
    }
}

Nous avons donc une interface IDataTransformer comprenant deux paramètres génériques T et K et une méthode qui va transformer une variable de type T en type K. Nous allons créer un type implémentant cette interface puis l’utiliser.

IDataTransformer<Manager, string> dataTransformer = new DataTransformer<Manager, string>(); // .NET 3.5 / 4 : OK
IDataTransformer<Person, string> dataTransformer2 = new DataTransformer<Manager, string>(); // .NET 3.5 / 4 : Erreur de compilation 
IDataTransformer<Manager, object> dataTransformer3 = new DataTransformer<Manager, string>(); // .NET 3.5 / 4 : Erreur de compilation 

Si le premier cas fonctionne logiquement, les deux cas suivants sont  moins logique. Dans le cas de dataTransfomer2, si on crée un DataTransformer prenant un Manager en entrée, on devrait aussi pouvoir prendre une personne (rappelons le, les managers sont des personnes).Pourtant le compilateur n’est pas de cette avis car il n’arrive pas à convertir DataTransformer<Manager,string> en IDataTransformer<Person,string>. De même pour dataTransformer3. Si on récupère en sortie une chaine de caractère, on pourrait très bien récupérer un object car string dérive de object.

Voila les limites de .NET 3.5 et voyons comment faire pour corriger cela en  .NET 4. Pour cela nous allons créer une nouvelle interface et une nouvelle classe Variant.

public interface IDataTransformerVariant<in T,out K>
{
    K Transform(T data);
}

  
public class DataTransformerVariant<T, K> : IDataTransformerVariant<T, K>
{
    public K Transform(T data)
    {
        return default(K);
    }
}

La différence se situe uniquement dans la déclaration de l’interface IDataTransformerVariant. On a rajouté un mot clé in devant le type d’entrée (T) et un out devant le type de sortie (K). En procédant ainsi, on active la covariance et la contravariance et on oblige le paramètre générique T a toujours être en paramètres de méthodes et le paramètre générique K a toujours être en valeur retour d’une méthode. Ne pas suivre cette règle entraine l’erreur de compilation suivante :

CoVarImg1

  • Conclusion

Pour conclure, je reprendrais les phrases importantes de cet article :

la covariance permet de caster un type générique A<T> dans un type générique A<K> si T hérite directement ou indirectement de K et si le type T est un paramètre de sortie.

La contravariance est la possiblité de caster un type générique A<T> dans un type générique A<K> si T est parent direct ou indirect de K et si T est un paramètre d’entrée.

Le fait que un paramètre générique soit d’entrée ou de sortie dépend de son placement dans les méthodes interfaces ou des délégués.

A titre d’information, ce concept est disponible depuis .NET 2.0 en IL (comme l’à montré Simon Ferquel dans son blog) mais aucun mot clé n’étant fourni en .NET, on ne pouvait pas l’utliser en C#.

Merci à Alexis Pera (MSP) pour la relecture et correction de cet article.

5 commentaires »

  1. […] @ 12:43 Suite de notre tour sur les nouveautés de C# 4. Après une présentation de la Co-Contravariance, nous allons nous intéresser à une fonctionnalité qui faisait cruellement défaut à .NET : les […]

    Ping par [Nouveautés C# .NET 4] Paramètres nommés et optionnels « Astuces DotNet (Sébastien Courtois) — 15/04/2010 @ 12:44

  2. […] This post was mentioned on Twitter by Arnaud Cleret and Sébastien O., Wilfried Woivré. Wilfried Woivré said: Co et contra variance http://tinyurl.com/y6wvudu par Sébastien Courtois […]

    Ping par Tweets that mention [Nouveautés C# .NET 4] Introduction à la Covariance / Contravariance « Astuces DotNet (Sébastien Courtois) -- Topsy.com — 28/04/2010 @ 16:32

  3. Pour la contravariance le bon exemple est :
    IDataTransformer dataTransformer2 = new DataTransformer(); // .NET 3.5 / 4 : Erreur de compilation

    Commentaire par aszan — 27/09/2011 @ 13:56

    • IDataTransformer { Person, string } dataTransformer2 = new DataTransformer { Manager, string } (); // .NET 3.5 / 4 : Erreur de compilation

      Commentaire par aszan — 27/09/2011 @ 13:57

  4. bonjour,

    Pour être convariant et contravariant en C# 3, il suffit de rajouter devant le type voulue un + ou un –

    A lire :

    http://www.simonferquel.net/blog/archive/2009/10/09/cocontra-variance–une-nouveauteacute-pas-si-nouvelle.aspx

    Commentaire par said — 12/08/2012 @ 11:28


RSS feed for comments on this post. TrackBack URI

Laisser un commentaire

Propulsé par WordPress.com.