Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

Actualités

  • Blog de Cyril DURAND, passionné de JavaScript, Ajax, ASP.net et tout ce qui touche au developpement Web Client-Side.

    View Cyril Durand's profile on LinkedIn

    hit counters

covariance et contravariance en .net : heritage avec des types generiques

Récemment, j'ai été confronté à un code du genre :

namespace ConsoleApplication60 { class Program { static void Main(string[] args) { List<Person> persons = new List<Person>(); // peuplement de la liste de person DoAction(persons); } /// <param name="persons"> /// Utilisation d'un IEnumerable<IPerson> plutot qu'une List<Person> pour être le plus /// générique possible et ainsi pouvoir lui passer un Person[] voir meme un Customer[] /// </param> static void DoAction(IEnumerable<IPerson> persons) { foreach (IPerson person in persons) { } } } public class Person : IPerson { public string FirstName { get { return "cyril"; } } } public interface IPerson { String FirstName { get; } } }

Avec ce code j'obtiens une erreur de compilation :

Argument '1': cannot convert from 'System.Collections.Generic.List<ConsoleApplication60.Person>' to 'System.Collections.Generic.IEnumerable<ConsoleApplication60.IPerson>'   

Mais pourquoi n'arrive t'il pas à convertir une List<Person> en IEnumerable<IPerson> ? pourtant List<T> implémente IEnumerable<T> et Person, IPerson. Tout semble pourtant OK.  

Pour bien comprendre le problème j'ai fait une petite recherche et je suis tombé sur ce thread : Casting generic collections & inheritance. En résumé, si on avait mis une List<Person> en argument de la méthode DoAction, le code plus haut ne compilerait pas non plus. Mais s'il compilait, cela voudrait dire que la méthode DoAction prendrait en paramètre une List<IPerson> qui pointe en fait vers une List<Person>. Pourtant rien ne nous empêche d'ajouter un objet de type Customer (qui implémente IPerson mais hérite pas de Person) à notre List<IPerson>. Il y aurait donc un non-sens car une List<Person> pourrais contenir un Customer !

Là où cela devient vraiment intéressant c'est avec ce post : Covariance and Contravariance in .NET, Java and C++. MSIL (Microsoft Intermediate Language) permet le code plus haut. Ce pseudo code C# est possible en MSIL, mais aucun langage ne l'implémente !

class Mammal { } class Dog : Mammal { } interface IReader<+T> // allows covariance { T GetValue(); } interface IWriter<-T> // allows contravariance { void SetValue(T value); } // With these definitions, covariance of generic parameters would allow this: IReader<Dog> dogReader = null; IReader<Mammal> mammalReader = dogReader; // Contravariance of generic parameters would allow this: IWriter<Mammal> mammalWriter = null; IWriter<Dog> dogWriter = mammalWriter;

Pour revenir au problème initial il suffit de spécifier le type final en "paramètre" de la méthode. Inutile ensuite de spécifier ce paramètre, l'inférence de type s'en chargera :

static void Main(string[] args) { List<Person> persons = new List<Person>(); // peuplement de la liste de person DoAction(persons); DoAction(new Person[3]); } static void DoAction<T>(IEnumerable<T> persons) where T : IPerson { foreach (IPerson person in persons) { } }
Posted: jeudi 28 juin 2007 18:50 par cyril
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :

Commentaires

Jb Evain a dit :

«En résumé, si on avait mis une List

<Person> en argument de la méthode DoAction, le code plus haut ne compilerait pas non plus. Mais s'il compilait, cela voudrait dire que la méthode DoAction prendrait en paramètre une List<IPerson> qui pointe en fait vers une List<Person>.»

Pas tout à fait, vu que même au niveau du CIL, on ne peut spécifier la covariance et la contravariance des paramètres génériques que sur les interfaces. Donc il faudrait utiliser IList et non pas List.

De plus, cela impliquerait que le premier paramètre générique des interfaces IEnumerable`1, ICollection`1 et IList`1 soit définit comme covariant, ce qu'ils ne sont pas :(

Bref, on est pas près de pouvoir utiliser ça réellement. À moins de tout ré-écrire System.Collections :)

# juin 28, 2007 21:55

cyril a dit :

Oui en effet mon exemple est pas très bien choisi, il faudrais redéfinir List

<T> :-)

Merci pour la précision sur le fait que cela ne s'applique que sur les interfaces. Il y a malheureusement très peu de documentation sur le sujet et je n'ai pas pris le temps de compiler de l'IL pour tester.

En tout cas je constate que l'IL à l'air de possèder pas mal de fonctionnalités que les langages .net n'implémentent pas.

# juin 28, 2007 22:19

FREMYCOMPANY a dit :

Quid du VB ?

# juin 29, 2007 11:57

cyril a dit :

La problématique initial était en VB ... le problème se pose (heureusement) aussi.

# juin 29, 2007 12:01

Mitsu a dit :

Salut Cyril,

C'est un pb épineux...mais logique.

Oublions les interfaces un moment et raisonnons avec des classes, le pb parait plus évident.

Je comprends aisément que les gens aient envie d'écrire:

Reader

<Dog> dogReader = null;

Reader<Mammal> mammalReader = dogReader;

Cela reviendrait à dire que Reader<B> hérite de Reader<A> si B hérite de A. La compatibilité des références semble apporter de la simplicité mais pourtant ce n'est pas possible. Comment compiler les méthodes virtuelles de la classe Reader dynamiquement alors que A et B sont génériques ?

Encore plus simple, que faire si Reader<T> a déjà une classe de base ?

Une solution élégante serait de surcharger la conversion de Reader<T> vers IReader<T> mais C# ne supporte pas les conversion vers ou depuis des interfaces...même combat.

Pour toutes ces raisons, C# ne supporte pour le moment la covariance que sur les interfaces (la notion d'héritage étant limitée à un simple "copier-coller" de méthodes).

Si tu cherches de bons exemples d'implémentation, regarde le framework lui-même: IQueryable<T> : IEnumerable<T> est un très bon exemple.

A plus et merci pour ton post intéressant,

Mitsu

# juin 30, 2007 10:40
Les commentaires anonymes sont désactivés

Les 10 derniers blogs postés

- [WPF] Nouvel article sur c2i.fr par Richard Clark le il y a 12 heures et 39 minutes

- F# nouvelle CTP 1.9.6.2 (update) par Pierrick's Blog le il y a 16 heures et 44 minutes

- La suite ...Proposition de collaboration rédactionnelle entre les communautés de développeurs et Microsoft France par LucasR le 09-05-2008, 17:45

- [Fun] Votre simulateur de vol avec Microsoft ESP par Julien Chable le 09-05-2008, 12:02

- [Best Practices] Customisation du My Site : Comment le modifier en amont et en aval par The Mit's Blog le 09-05-2008, 10:47

- Patrick Tisseghem s'en est allé ... par The Mit's Blog le 09-05-2008, 10:04

- MS AutoCollage par alex# le 09-05-2008, 09:18

- Un grand SharePointeur nous a quitte : Patrick Tisseghem manquera à la communauté ! par RedoBlog - The .NET Gentleman !!! le 09-05-2008, 08:52

- [WPF] Comment charger dynamiquement un fichier XAML qui définit des eventhandler ? par Thomas Lebrun le 09-04-2008, 10:56

- Article sur le filtrage des modèles de site SharePoint par The Grib's Lair [Sébastien PICAMELOT - MVP SharePoint] le 09-04-2008, 00:11