OVH Cloud OVH Cloud

C# 2.0 : Les class "Generic" : Ce qu’elles peuvent vous apporter

18 réponses
Avatar
Alexandre Roba
On a écrit beaucoup de chose sur les génériques et comme beaucoup d’autres
techniques, ils ont leurs défenseurs et leurs détracteurs. D’un coté il y a
les puristes de l’objet qui disent qu’ils ne respectent pas les règles du jeu
OO, mais de l’autre il y a les habitués des « Template » qui eux les trouvent
indispensable. Que peut-on donc nous en penser.

Personnellement j’essaye de rester pragmatique. J’y ai donc jeté un œil
attentif et j’ai essayé de voir comment je pouvais améliorer ma vie de
développeur avec ces class génériques.

<B>Introduction</b>

Les class génériques sont des squelettes de code ou le paramètre est un
type. Supposons que vous développiez une class contenant une liste d’entier.
Il serait pratique de pouvoir utiliser le même code pour définir une liste de
nombre décimaux.

Avec les génériques c’est plutôt facile. On commence par définir la classe
générique.

public class List<T>
{
private ArrayList list = new ArrayList();

public int Add(T input)
{
return this.list.Add(input);
}
public T this[int index]
{
get { return (T)list[index]; }
set { list[index] = value;}
}

}

Et puis pour l’utiliser avec un entier ou un nombre décimal on fait :

List<int> listentiers = new List<int>();
List<double> listdecimaux = new List<double>();

listentiers.Add(12);
listdecimaux.Add(12.3);

C’est aussi facile que ca.

Le paramètre <T> ne se limite pas, comme ici, à l’utilisation de type
simple. Notre générique peut être utilisé avec des types plus complexes tels
qu’une class ou une structure.

Voyons maintenant comment on peut en tirer profité dans notre vie de tous
les jours.

<b>Utilisation des classes génériques</B>
Lorsque j’ai besoin de créer une class qui représente une collection
d’entités, plusieurs choix s’offre à moi :

1. J’utilise une collection non typé comme ancêtre. L’inconvénient ici c’est
que la méthode me permettant d’ajouter des entités ne sera pas typée. Elle me
permettra d’ajouter des entités de n’importe qu’elle type. Bien sur je peux
faire des vérifications avant d’insérer l’élément et bien sur je peux ajouter
des méthodes d’ajout typé. Mais est-ce vraiment le plus pratique des moyens
?...
2. Je développe une class spécifique en incluant l’implémentation
d’interface de collection tels que « IEnumerable », « IList »… Je contrôle
donc tout l’implémentation ici mais je dois écrire pas mal de code.

Il existe d’autres moyens tels que créée sa propre class de collection
héritant de class comme « CollectionBase » et de faire hériter toutes vos
collections de cette nouvelle class… Mais ces moyens ne sont qu’une
combinaison des deux cas précédents.

Voyons comment les classes génériques peuvent nous faciliter la vie lors de
la création de collection typé.

Commençons par regarder quelques une des class génériques qui sont mises à
notre disposition avec le Framework .NET 2.0. On peut y voir :
• List<T>
• Dictionary<TKey,TValue>
• LinkedList<T>
• Queue<T>
• SortedDictionary<TKey,TValue>
• Stack<T>
• …

Concentrons nous sur List<T> pour construire une collection typé d’entité.
Soit « Personne » cette entité :
class Personne
{
private string prenom;
public string Prenom
{
get { return prenom; }
set { prenom = value; }
}
private string nomdefamille;
public string Nomdefamille
{
get { return nomdefamille; }
set { nomdefamille = value; }
}
}

Créons notre liste typé.

List<Personne> personnes = new List<Personne>();

Ajoutons un élément à cette liste :

personnes.Add(new Personne());
personnes[0].Prenom = "Albert";
personnes[0].Nomdefamille = "Einstein";

Pratique non ? Et on peut bien sur faire la même chose pour une pile, une
queue, une liste ordonnée…

Lorsque l’on veut parcourir une liste non typé en utilisant « foreach() » il
suffit que notre la liste implémente l’interface « IEnumerable » qui est non
typé.

Arraylist list = new ArrayList();
foreach (int i in list){...}
foreach (object o in list){...}
foreach (mauvaisobject o in list){...}

Fonctionnera parfaitement à la compilation…Mais lors de l’exécution on peut
avoir des surprises…

Pour remédier à ce problème, nous avons maintenant à notre disposition
l’interface « IEnumerable<> » Chaque class générique qui implémente cette
interface devient parcourable de manière typé !

//Fonctionne
foreach (Personne p in personnes){...}
//Erreur de compilation
foreach (int i in personnes){...}

Ce qui nous permet d’être certain lors de la compilation du type qui se
trouve réellement dans la liste.
Je ne sais pas pour vous mais je pense que rien que ca va me permettre de
rendre mon code plus solide.

Dans le namespace « System.Collection.Generic » nous pouvons aussi trouver
la version générique des interfaces suivantes :
• ICollection<>
• IDictionary<>
• IList<>
• IComparer<>
• IEnumerator<>
• IEqualityComparer<>

<b>Pour conclure</B>

Après avoir passé quelque temps sur les classes génériques je peux dire
maintenant qu’elles me feront gagner énormément de temps.
D’abord parce que le Framework contient certaines des classes du namespace
« System.Collection » implémenté avec des génériques me donnant ainsi un
moyen rapide et facile de les utiliser. Ensuite parce que je pense qu’une
bonne utilisation des classes génériques peut m’amener à un meilleur niveau
de réutilisation de code.

8 réponses

1 2
Avatar
Cyrille37
Oriane wrote:
Pour avoir utilisé des templates C++, je suis assez dubitatif par rap port à
ces techniques, qui donnent (dans le cas de C++) un code plus lent, un
débuggage plus difficile.



Ha bon.
J'aurais pensé que ça donnerait du code plus rapide,
car le typage serait fait à la compilation donc pas de transtypage à l'exécution.
Mince alors ...

cyrille
Avatar
Ambassadeur Kosh
> Pour avoir utilisé des templates C++, je suis assez dubitatif par rapport
à ces techniques, qui donnent (dans le cas de C++) un code plus lent, un
débuggage plus difficile.



je croyais que la compilation classique, c'était une "réecriture" de la
classe en substituant "textuellement" le parametre generique par sa
valeur... du coup, vu que l'optimisation passe dessus au meme titre que le
reste, le code résultant devrait etre le même...

on m'aurait menti ?
Avatar
Ambassadeur Kosh
> Es-tu sur qu'il s'agisse de typage dynamique? Je pense que le typage
dynamique siginifique que l'object se voit attribuer son type lors de sa
creation et qu'il se voit capable de dire quel est son type. Le fait qu'il
implemente une methode ne donne pas son type our autant.



nan, il parle de langages, disons, à la Caml

T plus<T>(T a,T b)
{
return a+b ;
}

int n = plus<int>(x,y) ;

en C++, la résolution trouve un + pour int quand elle "construit" int
plus<int>(int a,int b) comme si c'etait une substitution textuelle de T en
int puis relancer la compil (dynamique)
en C#, le compilo dit erreur car le + doit provenir du contrat que tu passes
sur T par le where, le contrat étant (pour le moment) une classe ou une
interface de base, et deux ou trois autres trucs (statique)

Meyer et Colnet avaient déja eu la même discussion en 1996 à propos d'Eiffel
lors du choix à faire pour l'implantation de la genericité.
en fait, ces deux choix semblent etre deux cas particuliers d'un ensemble
plus riche (on en a parlé y'a pas longtemps d'ailleurs).

tout ça pour dire que les generics en 2.0, c'est la grande class...
Avatar
Oriane
"Ambassadeur Kosh" a écrit dans le message de
news:
Pour avoir utilisé des templates C++, je suis assez dubitatif par rapport
à ces techniques, qui donnent (dans le cas de C++) un code plus lent, un
débuggage plus difficile.



je croyais que la compilation classique, c'était une "réecriture" de la
classe en substituant "textuellement" le parametre generique par sa
valeur... du coup, vu que l'optimisation passe dessus au meme titre que le
reste, le code résultant devrait etre le même...

on m'aurait menti ?


Précisons, car j'ai été inexact.
J'ai utilisé un Outil d'Ilog en C++, en 98, avec une foultitude de
template.
A l'époque, il fallait au moins 1 Go de mémoire pour compiler leur code sous
Visual Studio 5.0. Et encore ca prenait du temps.
Le débuggage était impossible puisque le débuggeur faisait référence à du
code généré par le template lors de la compilation.
Lorsqu'on est passé à un compilateur sur Unix on a atteint le sommet de
l'horreur car il y avait des incompatibilités pour cause de normes pas tout
à fait respectées (par le compilateur HP, dixit Ilog évidemment).
Et puis des templates de références sur des pointeurs d'élements de type
référence de pointeur, je suis pas spécialement nulle en théorie en C++,
mais ca devenait incompréhensible. C'est heureusement plus simple en C#.

Par contre, au niveau exécution je sais pas, c'était sans doute aussi
rapide.

Voilà, depuis je me méfie.

A+
Avatar
Sylvain Lafontaine
Ce n'est pas le typage dynamique, c'est les fonctions virtuelles. La
définition « un objet est du bon type s'il possède la méthode que l'on
souhaite lui voir exécuter » n'est bonne que dans le cas des fonctions
virtuelles et ne sera pas toujours valide dans le cas des fonctions
statiques. (Par fonction statique, j'entends ici une fonction qui n'a pas
été déclarée virtuelle dans la classe courante ou une des classes de base et
non pas une fonction qui accède uniquement à des variables statiques de la
classe. Il y a, je crois, un meilleur mot que statique mais ma mémoire me
fait défaut en ce moment.)

Smaltalk n'implémente que des fonctions virtuelles, il n'y a donc pas de
différence avec le typage dynamique. C++ implémente les deux, il faut donc
prendre soin de choisir de caster ou non selon chacun des cas. C#
implémente également les deux mais dans son cas la confusion est beaucoup
moins grande car on doit préciser la propriété « override » dans les classes
dérivées pour les fonctions qui ont été déclarées virtuelles dans une des
classes de base.

Pour ce qui est des templates, le type-safety ne fonctionne que dans les cas
simples; la où un seul niveau de hiérarchie est utilisée. Si on veut
utiliser dans le même template des classes de base et des classes dérivées,
on se retrouve devant la même situation que dans le cas de l'utilisation des
références sans template; alors je ne vois vraiment pas pourquoi certains
les considèrent si supérieurs que cela. Ils peuvent devenir lègèrement
utiles (mais pas essentiels) quelquefois mais plus souvent qu'autrement; ils
vont rendre le code quasiment illisible.

--
Sylvain Lafontaine, ing.
MVP - Technologies Virtual-PC
E-mail: http://cerbermail.com/?QugbLEWINF


wrote in message
news:

Cyrille37 a écrit :

Arnaud Debaene wrote:
> Alexandre Roba wrote:
>
>>On a écrit beaucoup de chose sur les génériques et comme beaucoup
>>d'autres techniques, ils ont leurs défenseurs et leurs détracteurs.
>>D'un coté il y a les puristes de l'objet qui disent qu'ils ne
>>respectent pas les règles du jeu OO,
>
>
> Je serais assez intéressé par une argumentation sérieuse de cette
> "thèse"...
> L'un des premiers apports des génériques, c'est la "type-safety" des
> collections par rapport à une implémentation basée sur Object qui
> réclame
> des downcasts dans tous les sens. Si la "type-safety" n'est pas "dans
> les
> règles de jeu OO" (c'est quoi çà au juste ces règles du jeu????), je
> pense
> qu'on a pas la même idée de ce que c'est que l'objet....

Bien d'accord avec Arnaud.

Et j'apporte un peu d'essence sur le feu en ajoutant :

Tout dépend de l'école OO à laquelle on se réfère, Comme d'habitude il y
en a plusieures. ;o)

Il y a notamment une des philosophies Orientée Objet qui définie le typage
de cette façon :
un objet est du bon type s'il possède la méthode que l'on souhaite lui
voir exécuter.
Il me semble que ça vient de SmallTalk, puis Javascript. Corrigez moi si
je me trompe.



Oui, 'est le typeage dynamique, mais il se trouve que .NET (et C#) ont
pris l'option du typage statique (en fournissant des capacités de
réflexion et de downcasts dynamiques avec vérification du type réel
de l'objet, mais ca reste du typage statique).

Arnaud
MVP - VC
Avatar
Ambassadeur Kosh
> Et puis des templates de références sur des pointeurs d'élements de type
référence de pointeur, je suis pas spécialement nulle en théorie en C++,
mais ca devenait incompréhensible. C'est heureusement plus simple en C#.



j'adhere.
C# est un vrai régal : avec des event et des class, c'est tout aussi
structuré.

Par contre, au niveau exécution je sais pas, c'était sans doute aussi
rapide.



bon, c'est pas la dessus qu'on gagne de toute façon. 5%, ça peut pas luter
contre une réecriture...

Voilà, depuis je me méfie.



yep
Avatar
Sylvain Lafontaine
Sauf pour les cas simples, le transtypage à l'exécution sera quand même
utilisé pour les templates car le compilateur ne sauras pas si vous lui
envoyer un objet d'une classe dérivée ou non dans les pattes.

La majorité des discussions au sujet des templates (ou classes génériques)
excluent ou ne mentionnent pas l'utilisation des classes dérivées; d'où
l'apparence d'utilité des templates. Lorsque vous mettez dans le portrait
l'utilisation de classes dérivées, bien des conclusions à leur sujet
changent subitement.

--
Sylvain Lafontaine, ing.
MVP - Technologies Virtual-PC
E-mail: http://cerbermail.com/?QugbLEWINF


"Cyrille37" wrote in message
news:
Oriane wrote:
Pour avoir utilisé des templates C++, je suis assez dubitatif par rapport
à ces techniques, qui donnent (dans le cas de C++) un code plus lent, un
débuggage plus difficile.



Ha bon.
J'aurais pensé que ça donnerait du code plus rapide,
car le typage serait fait à la compilation donc pas de transtypage à
l'exécution.
Mince alors ...

cyrille
Avatar
Ambassadeur Kosh
"Sylvain Lafontaine" <sylvain aei ca (fill the blanks, no spam please)> a
écrit dans le message de news: %
Ce n'est pas le typage dynamique, c'est les fonctions virtuelles. La
définition « un objet est du bon type s'il possède la méthode que l'on
souhaite lui voir exécuter » n'est bonne que dans le cas des fonctions
virtuelles et ne sera pas toujours valide dans le cas des fonctions
statiques.



ça d'accord, mais j'ai du mal de voir ou les generics viennent mettre le
grain de sable la dedans.

(Par fonction statique, j'entends ici une fonction qui n'a pas été
déclarée virtuelle dans la classe courante ou une des classes de base et
non pas une fonction qui accède uniquement à des variables statiques de la
classe. Il y a, je crois, un meilleur mot que statique mais ma mémoire me
fait défaut en ce moment.)



j'ai lu "fonctions non-virtuelles" et "résolution statique".
par oppositon à ploymorphisme, peut on parler de monomorphisme ?
enfin bon, on s'est compris....

Smaltalk n'implémente que des fonctions virtuelles, il n'y a donc pas de
différence avec le typage dynamique. C++ implémente les deux, il faut
donc prendre soin de choisir de caster ou non selon chacun des cas. C#
implémente également les deux mais dans son cas la confusion est beaucoup
moins grande car on doit préciser la propriété « override » dans les
classes dérivées pour les fonctions qui ont été déclarées virtuelles dans
une des classes de base.



certes, maid quid les generics et la non OO compliance

Pour ce qui est des templates, le type-safety ne fonctionne que dans les
cas simples; la où un seul niveau de hiérarchie est utilisée. Si on veut
utiliser dans le même template des classes de base et des classes
dérivées, on se retrouve devant la même situation que dans le cas de
l'utilisation des références sans template; alors je ne vois vraiment pas
pourquoi certains les considèrent si supérieurs que cela. Ils peuvent
devenir lègèrement utiles (mais pas essentiels) quelquefois mais plus
souvent qu'autrement; ils vont rendre le code quasiment illisible.



ok. un exemple miniature pour mettre en valeur tout ça ?

quand à l'isibilité, List<decimal>, List<double>, c'est pas un coup une
DoubleList, un coup une DoubleCollection, un coup une CollectionOfDouble...
ce qui compte c'est que ça soit typé et que l'erreur sémantique soit revelée
par le compilateur plutot que "peut être avec un peu de chance" par les
tests et (trop) souvent chez le client...
1 2