Paru le 08 mars 2007
Auteur : Mitsuru Furuta, Relation Technique avec les Développeurs
Sur cette page
Introduction
Les itérateurs
Comparaison des collections et des énumérations par l’exemple
Conclusion
Introduction
Dans un très grand nombre de langages, nous sommes habitués à utiliser des collections pour regrouper nos objets. Nous les utilisons aussi bien dans les couches basses, dans les frameworks, dans les composants ou dans les couches de présentation. Le framework .Net lui-même utilise et fournit massivement des collections d’objets. Nous appelons aujourd’hui collection, une structure de type tableau dynamique fournissant une liste d’éléments et offrant les capacités plus ou moins riches d’ajout, de suppression, de tri ou encore de filtrage.
Une autre solution existe cependant depuis longtemps. Ce sont les itérateurs. Les plateformes telles que .Net ou java offre ce mécanisme depuis leur origine. De plus ancienne génération encore, nous utilisions fréquemment des curseurs unidirectionnels.
Les itérateurs
.Net généralise ce mécanisme à travers l’interface IEnumerator.
En voici la définition :
// Summary:
// Supports a simple iteration over a nongeneric collection.
public interface IEnumerator {
// Summary:
// Gets the current element in the collection.
//
// Returns:
// The current element in the collection.
//
// Exceptions:
// System.InvalidOperationException:
// The enumerator is positioned before the first element of the collection or
// after the last element.
object Current { get; }
// Summary:
// Advances the enumerator to the next element of the collection.
//
// Returns:
// true if the enumerator was successfully advanced to the next element; false
// if the enumerator has passed the end of the collection.
//
// Exceptions:
// System.InvalidOperationException:
// The collection was modified after the enumerator was created.
bool MoveNext();
//
// Summary:
// Sets the enumerator to its initial position, which is before the first element
// in the collection.
//
// Exceptions:
// System.InvalidOperationException:
// The collection was modified after the enumerator was created.
void Reset();
}
L’idée du curseur unidirectionnel est d’être léger. Nous pouvons à travers cette interface, placer l’itérateur en position de départ, avancer le curseur d’un élément et lire l’élément courant.
Nous notons que nous ne pouvons pas connaitre le nombre d’éléments avant d’avoir atteint la fin de l’itérateur. Ce point déterminant nous indique que l’objet répondant à cette interface n’a pas besoin d’être une structure de stockage (tableau, collection).
L’avantage en est qu’un objet de parcours de données n’ayant aucune idée de la longueur de la série qu’il parcourt peut implémenter une telle interface (ex : parcourir un DataReader, lire un flux réseau).
Du côté du langage, mis à part le fait d’appeler les méthodes de l’itérateur dans une classique boucle « while », nous pouvons utiliser l’instruction « foreach » en implémentant IEnumerable.
Ainsi, cette écriture :
IEnumerator enumeration = ObjetQuiImplementeIEnumerator;
enumeration.Reset();
while (enumeration.MoveNext()) {
object o = enumeration.Current;
}
Peut se simplifier en :
IEnumerator enumeration = ObjetQuiImplementeIEnumerator;
foreach (string s in enumeration) {
}
L’interface IEnumerable offre une unique méthode GetEnumerator() qui retourne la fameuse interface IEnumerator. Nous pouvons donc considérer IEnumerable comme étant un fournisseur d’IEnumerator.
L’instruction « foreach » offre également l’avantage d’inclure dans sa syntaxe un transtypage (cast) («string» dans notre exemple).
Comparaison des collections et des énumérations par l’exemple
Voici l’énoncé de la problématique : dans une application Windows Forms, offrir une méthode renvoyant la liste de tous les contrôles enfants d’un contrôle parent donné (de manière récursive, comprenant donc les enfants des enfants, etc). Pour rappel, la classe Control offre une propriété Controls qui renvoie la liste des enfants directs de ce contrôle.
La méthode suivante offre une première solution basée sur des collections.
Chaque calcul intermédiaire des différentes étapes de la récursivité renvoie la liste des contrôles enfants. Chaque sous-liste est ajoutée au contrôle parent lors du « controls.AddRange(GetAllControls(childControl))».
private List<Control> GetAllControls(Control parentControl) {
List<Control> controls = new List<Control>();
foreach (Control childControl in parentControl.Controls) {
controls.Add(childControl);
controls.AddRange(GetAllControls(childControl));
}
return controls;
}
Cette implémentation est tout à fait correcte mais nous allons voir que la seconde solution basée sur les itérateurs est plus puissante. La voici :
private IEnumerable<Control> EnumerateAllControls(Control parentControl) {
foreach (Control childControl in parentControl.Controls) {
yield return childControl;
foreach (Control c in EnumerateAllControls(childControl))
yield return c;
}
}
Je ne rentrerai pas lors de cet article dans les explications du mot clé yield qui simplifie l’écriture des itérateurs. Vous pouvez cependant vous référer à ce webcast dans lequel j’explique son fonctionnement (http://www.microsoft.com/france/Vision/List.aspx?Did=B2873510-DC97-4F45-B64D-A3DD3C0DB48f&Pid=&Tid=&Cid=D6056886-8F18-4B74-822E-1EE229AE811a, « Partie 11 : Itérations – Rappels »).
Pourquoi cette solution est-elle plus puissante. Vous remarquerez déjà que le résultat à l’exécution est le même (la solution est jointe à cet article). En incrémentant un compteur à chaque création de collection lors de la première solution, nous mesurons que 13 collections ont été crées, consommant temps processeur et mémoire. Dans la seconde solution, aucune collection n’est créée. L’avantage semble alors évident.
Si jamais nous avons besoin de fournir une collection à n’importe quelle étape du parcours, il suffit d’appeler « new List<Control>(enumeration); ».
Conclusion
Nous avons donc avec la seconde solution, une approche plus légère et plus bas niveau qui est plus rapide et ne consomme pas de structures intermédiaires. De plus, nous pouvons à tout moment fournir une collection remplie grâce à notre itérateur et offrant ainsi les mêmes avantages que la première solution.
Téléchargez
Sources.zip
73 Ko