Après l’écriture du post précédent concernant la surveillance de l’énumération d’une collection au moyen de délégués, il apparait que la méthode (simple) mise en œuvre peut largement être étendue jusqu’à construire un ensemble complet de méthodes d’extension de IEnumerable<T> agissant non pas sur la collection elle-même comme les méthodes de System.Linq.Enumerable, mais sur son énumération. Par exemple, on peut parfaitement créer une nouvelle méthode EnumerateIf permettant de spécifier une condition lors de l’énumération. Si la condition n’est pas satisfaite, l’énumération sur l’élément courant de la collection est ignorée. Pour implémenter EnumerateIf, il suffit de déclarer la méthode d’extension suivante :
public static IEnumerable<TSource> EnumerateIf<TSource>(this IEnumerable<TSource> source,
Predicate<TSource> predicate)
{
EEnumerable<TSource> eenumerable = new EEnumerable<TSource>(source,
predicate);
return eenumerable;
}
Le nouveau constructeur d’EEnumerable<T> transmet la condition à son enumerator :
public EEnumerable(IEnumerable<TSource> source,
Predicate<TSource> enumerationCondition)
{
Source = source;
Enumerator = new EEnumerator<TSource>(source.GetEnumerator(), enumerationCondition, this);
}
Et le nouveau constructeur d’EEnumerator<T> affecte simplement le prédicat :
public Predicate<TSource> EnumerationCondition { get; set; }
public IEnumerator<TSource> Source { get; set; }
private EEnumerable<TSource> parent;
public EEnumerator(IEnumerator<TSource> source,
Predicate<TSource> enumerationCondition,
EEnumerable<TSource> parent)
{
Source = source;
EnumerationCondition = enumerationCondition;
this.parent = parent;
}
Comme pour le post précédent, la vraie logique se trouve en fait dans la méthode MoveNext, appelée effectivement au fur et à mesure de l’énumération. Ici, les lignes 6 à 8 réalisent le comportement souhaité en “sautant” les éléments de la Source interne qui ne satisfont pas la condition :
1: public bool MoveNext()
2: {3: if (OnItemEnumerating != null)
4: OnItemEnumerating(Current); 5: 6: bool result = Source.MoveNext();
7: while (EnumerationCondition != null && !EnumerationCondition(Current) && result)
8: result = Source.MoveNext(); 9: 10: if (OnItemEnumerated != null)
11: OnItemEnumerated(Current);12: if (!result && parent.OnEnumerated != null)
13: parent.OnEnumerated(parent);14: return result;
15: }Ainsi, avec le code d’exemple suivant :
List<int> ints = new List<int>();
ints.Add(1);
ints.Add(2);
ints.Add(3);
ints.Add(4);
ints.EnumerateIf(i => i >= 3)
.Monitor(i => Console.WriteLine("Monitoring collection"),
i => Console.WriteLine("Collection monitored"),
i => Console.WriteLine("Current item is " + i),
i => Console.WriteLine("Next item will be " + i))
.ToList();
Le résultat affiché dans la console est bien le résultat attendu :
La méthode EnumerateIf n’est qu’un premier exemple. D’autres méthodes du même style sont envisageables :
- EnumerateWhile
- EnumerateRange (en spécifiant l’index minimal et maximal, en vue d’une pagination)
- EnumerateWithDelay (ajout d’un Timer pour insérer une délai entre chaque appel à MoveNext)
- EnumerateDuring (énumérer autant d’éléments que possible en un temps donné)
- etc.
Evidemment, il reste à trouver des cas d’utilisation concrets pour ces différentes méthodes, mais d’une façon générale, il devient possible d’insérer du comportement pendant l’énumération des collections pour réaliser un traitement “ultime” au moment de la récupération des données.
Ainsi, à partir d’un ensemble de données disponibles (stockées par exemple dans un cache), on peut réaliser le filtrage (EnumerateIf) et la pagination (EnumerateRange) sans avoir besoin de créer une nouvelle collection (habituellement par les méthodes Where, Skip et Take), mais en modifiant uniquement la façon dont les données sont énumérées.