juillet
2009
Les itérateurs sont des éléments essentiels du framework .net. Toutes les collections fournies par le framework implémentent cette facilité pour les développeurs. Simples à utiliser, permettant de produire un code élégant et léger, ils sont cependant plus rares dans les collections spécifiques mises en place par les développeurs. La faute, certainement à un passif lourd dans la première version du framework, où la mise en place était assez laborieuse. La version 2 du framework simplifie les choses en introduisant le mot clé yield : ce raccourci permet d’éviter de coder toute la mécanique sous-jacente (c’est le compilateur c# qui s’en charge).
Cependant, pour le développeur soucieux de fournir des classes « propres », il reste un problème génant : à moins de passer par des types génériques, les itérateurs ne permettent pas de proposer une approche fortement typée.
Nous allons voir comment, en quelques lignes, corriger ce problème.
Typiquement, l’utilisation d’un itérateur « maison » se fait de la façon suivante :
{
maClassePerso o_tmp = (maClassePerso)o;
o_tmp.MethodePerso();
}
Chaque élément que l’on récupère dans notre boucle foreach n’est pas nativement fortement typé, et l’on est obligé de faire un cast dans le corps de la boucle.
Pourtant, il existe une astuce, qui fonctionne aussi bien en c# 1.0 qu’en 2.0, qui permet de récupérer directement un objet correctement typé.
Imaginons que nous ayons 2 classes à coder : une classe « Personne », et une classe « Famille » ; la famille étant une collection de personnes.
La classe « Personne » est implémentée de la façon suivante :
{
public string Nom;
public string Prenom;
public Personne()
{
}
public Personne(string nom, string prenom)
{
this.Nom = nom;
this.Prenom = prenom;
}
public string DonneTonNom()
{
return this.Prenom + " " + this.Nom;
}
}
La classe « Famille » utilise un arraylist pour stocker les personnes de la famille :
{
System.Collections.ArrayList lesPersonnes;
public Famille()
{
lesPersonnes = new System.Collections.ArrayList();
}
public void Ajouter(Personne unePersonne)
{
lesPersonnes.Add(unePersonne);
}
}
L’objectif de codage de notre énumérateur pour la classe “Famille” est simple : il faut pouvoir écrire la boucle suivante :
{
static void Main(string[] args)
{
Personne p1 = new Personne("Mansoif", "Gérard");
Personne p2 = new Personne("Scroutedansmonsacado", "Jessica");
Personne p3 = new Personne("Mouraiparti", "Mona");
Famille laFamille = new Famille();
laFamille.Ajouter(p1);
laFamille.Ajouter(p2);
laFamille.Ajouter(p3);
foreach (Personne unePersonne in laFamille)
{
System.Console.WriteLine(unePersonne.DonneTonNom());
} Console.Read();
}
}
Il n’y a pas de cast dans notre boucle foreach.
Normalement, pour coder un iterateur, il faut implémenter l’interface system.collections.IEnumerable. Mais cette interface nous oblige à renvoyer des éléments de type object.
La solution, pour arriver à notre exemple d’utilisation, est très simple : il suffit de ne pas implémenter IEnumerable !
En effet, pour que notre boucle foreach fonctionne, il faut (et il suffit) que la classe «Famille » implémente les méthodes GetEnumerator, MoveNext, Reset, et qu’elle fournisse un champ Current. Et en interne, il suffit de stocker l’indice de l’élément courant de notre itération.
L’écriture de ces méthodes est toute simple, et se fait, contrairement à l’implémentation classique en framework 1, sans l’utilisation de « nested class ».
On a donc, au final, la classe famille suivante :
{
System.Collections.ArrayList lesPersonnes;
public Famille()
{
lesPersonnes = new System.Collections.ArrayList();
}
public void Ajouter(Personne unePersonne)
{
lesPersonnes.Add(unePersonne);
}
#region implementation de notre itérateur
int index = -1; // Permet de stocker l’index de l’élément courant
public Famille GetEnumerator()
{
return this;
}
public Personne Current
{
get
{
return (Personne)lesPersonnes[index];
}
}
public bool MoveNext()
{
index++;
if (index < lesPersonnes.Count) return true;
else return false;
}
public void Reset()
{
index = -1;
}
#endregion
}
La seule subtilité du code ci-dessus est dans la méthode GetEnumerator : on renvoie directement l’instance de Famille utilisée : c’est cette classe qui contient directement l’implémentation de l’iterateur.
L’intérêt de ne pas implémenter IEnumerable se voit dans le type de retour de notre propriété Current : on renvoie directement un objet de type Personne, le cast se faisant directement dans l’assesseur get.
Il faut quand même noter que, si on n’implémente pas IEnumerable, on ne peut pas utiliser le mot clé yield : ce mot clé est en effet un indicateur qui, associé à IEnumerable, permet au compilateur de créer lui-même l’implémentation façon framework 1 de l’itérateur. Mais le code à écrire étant trivial, cela ne pose pas trop de problème.
Si ils sont toujours en 1.1 (ou en 1.0, hérésie !!!), oui…
(et on ne rigole pas, ça arrive encore )
Elle est bien, cette astuce, ça donne un gout de généricité au fw 1.1 (m’aurait bien servi à l’époque )
Il y’a encore des gens qui utilise ArrayList ?
L’intérêt de cette solution est d’empécher d’écrire le code suivant :
// la classe uneAutreClasse n'a rien avoir avec maClassePerso <br />
foreach (uneAutreClasse o in maCollectionPerso) <br />
{ <br />
o.uneMethode(); <br />
} <br />
la boucle génère dans ce cas une erreur à la compilation.
Au temps pour moi!
J’ai mal lu …
« il reste un problème génant : à moins de passer par des types génériques«
Je ne vois pas trop pourquoi tu ne pourrais pas faire ceci?
foreach (maClassePerso o in maCollectionPerso) <br />
{ <br />
o_tmp.MethodePerso(); <br />
} <br />