Implémentation d’un énumérateur bidirectionnel en C #

Existe-t-il un moyen d’utiliser des blocs de rendement pour implémenter un IEnumerator qui peut revenir en arrière ( MoveLast() ) et en avant?

Pas directement depuis le bloc iterator, non.

Cependant, l’appelant peut toujours mettre les résultats en mémoire tampon, par exemple dans une List , ou simplement appeler Reverse() – mais cela ne s’applique pas toujours.

Non, la machine à états générée par le compilateur C # est ssortingctement en avant.

Cela n’a même pas de sens de revenir en arrière dans de nombreux cas. Imaginez un iterator lisant un stream de réseau – pour revenir en arrière, il devrait se rappeler de tout ce qu’il a déjà lu, car il ne pouvait pas revenir en arrière et demander à nouveau les données au réseau.

(Idem pour tout ce qui génère des données avec une certaine perte. Imaginez un iterator qui renvoie un nouveau tableau pour Life de Conway à chaque itération – il existe plusieurs tableaux qui auraient tous pu être précédents , alors pour revenir en arrière, vous devez vous ‘ai déjà retourné.)

Je sais que ce fil est super vieux mais il est important de noter que

 foreach(var item in someCollection) { // Do something } 

… est compilé dans:

 var enumerator = someCollection.GetEnumerator() while (enumerator.MoveNext()) { var item = enumerator.Current; // Do something } 

Donc, si la syntaxe “MoveNext” ne vous dérange pas, vous pouvez facilement implémenter IEnumerator et append un “MovePrevious”. Vous ne pourrez pas inverser la direction si vous utilisez “foreach”, mais vous pourrez inverser la direction si vous utilisez une boucle while.

Ou … si vous souhaitez “consulter” une liste en sens inverse (non bidirectionnel), vous pouvez tirer parti de la déclaration de rendement.

 public static IEnumerable Get(IList list) { if (list == null) yield break; for (int i = list.Count - 1; i > -1; i--) yield return list[i]; } 

Ou bien … si vous voulez éviter en sens inverse en empruntant la longue route, vous pouvez implémenter votre propre IEnumerable / IEnumerator

 public static class ReverseEnumerable { public static IEnumerable Get(IList list) { return new ReverseEnumerable(list); } } public struct ReverseEnumerable : IEnumerable { private readonly IList _list; public ReverseEnumerable(IList list) { this._list = list; } public IEnumerator GetEnumerator() { if (this._list == null) return Enumerable.Empty().GetEnumerator(); return new ReverseEnumator(this._list); } IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } } public struct ReverseEnumator : IEnumerator { private readonly IList _list; private int _currentIndex; public ReverseEnumator(IList list) { this._currentIndex = list.Count; this._list = list; } public bool MoveNext() { if (--this._currentIndex > -1) return true; return false; } public void Reset() { this._currentIndex = -1; } public void Dispose() { } public TItem Current { get { if (this._currentIndex < 0) return default(TItem); if (this._currentIndex >= this._list.Count) return default(TItem); return this._list[this._currentIndex]; } } object IEnumerator.Current { get { return this.Current; } } } 

La bibliothèque de collections C5 ( http://www.itu.dk/research/c5/ ) implémente les collections et la liste chaînée avec une énumération arrière. Le projet est OpenSource, vous devriez donc pouvoir y répondre.

Non. L’une des limites d’IEnumerator est qu’il conserve son état actuel et ne se souvient pas de son état antérieur. En conséquence, IEnumerable est en aval uniquement.

Si vous devez conserver des états antérieurs, lisez IEnumerable dans une liste ou une liste liée et énumérez-les plutôt.

En fait, il semble exister une approche décrite dans Accelerated C # 2008 . Malheureusement, deux pages ne sont pas visibles dans l’aperçu et il doit s’appuyer sur une reflection (dont les résultats peuvent être mis en cache, comme d’habitude), mais vous pouvez en obtenir l’essentiel.

Non. L’utilisation du yield donne un IEnumerable qui est unidirectionnel.