Escribiendo IEnumerator personalizado con iteradores

¿Cómo puedo escribir una IEnumerator personalizada de IEnumerator que necesite mantener un cierto estado y seguir utilizando bloques de iteradores para simplificarlo? Lo mejor que se me ocurre es algo como esto:

 public class MyEnumerator : IEnumerator { private IEnumerator _enumerator; public int Position {get; private set;} // or some other custom properties public MyEnumerator() { Position = 0; _enumerator = MakeEnumerator(); } private IEnumerator MakeEnumerator() { // yield return something depending on Position } public bool MoveNext() { bool res = _enumerator.MoveNext(); if (res) Position++; return res; } // delegate Reset and Current to _enumerator as well } public class MyCollection : IEnumerable { IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public MyEnumerator GetEnumerator() { return new MyEnumerator(); } ... } 

¿Por qué quieres escribir una clase de iterador? El punto entero de un bloque de iteradores es para que no tenga que …

es decir

 public IEnumerator GetEnumerator() { int position = 0; // state while(whatever) { position++; yield return ...something...; } } 

Si agrega más contexto (i, e, por qué lo anterior no puede funcionar), probablemente podamos ayudar más.

Pero si es posible, evita escribir una clase de iterador. Son mucho trabajo, y es fácil equivocarse.

Por cierto, no es necesario que te preocupes por Reset , ya que está en gran parte en desuso, y nunca deberías usarlo (ya que no se puede confiar en que funcione para un enumerador arbitrario).

Si quieres consumir un iterador interno, eso también está bien:

 int position = 0; foreach(var item in source) { position++; yield return position; } 

o si solo tienes un enumerador:

 while(iter.MoveNext()) { position++; yield return iter.Current; } 

También podría considerar agregar el estado (como una tupla) a lo que produce:

 class MyState { public int Position {get;private set;} public T Current {get;private set;} public MyState(int position, T current) {...} // assign } ... yield return new MyState(position, item); 

Finalmente, podría usar un enfoque de extensión / delegado de estilo LINQ, con una Action para proporcionar la posición y el valor a la persona que llama:

  static void Main() { var values = new[] { "a", "b", "c" }; values.ForEach((pos, s) => Console.WriteLine("{0}: {1}", pos, s)); } static void ForEach( this IEnumerable source, Action action) { if (source == null) throw new ArgumentNullException("source"); if (action == null) throw new ArgumentNullException("action"); int position = 0; foreach (T item in source) { action(position++, item); } } 

Salidas:

 0: a 1: b 2: c 

Tendría que coincidir con Marc aquí. O bien escribe una clase de enumerador completamente si realmente quieres (¿solo porque puedes?) O simplemente usa un bloque de interator y declaraciones de rendimiento y termina con eso. Personalmente, nunca voy a tocar las clases de enumerador de nuevo. 😉

@Marc Gravell

Pero si es posible, evita escribir una clase de iterador. Son mucho trabajo, y es fácil equivocarse.

Es precisamente por eso que quiero usar maquinaria de yield dentro de mi iterador, para hacer el trabajo pesado.

También podría considerar agregar el estado (como una tupla) a lo que produce:

Sí, eso funciona. Sin embargo, es una asignación adicional en cada paso. Si solo estoy interesado en T en la mayoría de los pasos, eso es un gasto general que no necesito si puedo evitarlo.

Sin embargo, tu última sugerencia me dio una idea:

 public IEnumerator GetEnumerator(Action action) { int position = 0; // state while(whatever) { position++; var t = ...something...; action(t, position); yield return t; } } public IEnumerator GetEnumerator() { return GetEnumerator(DoNothing()); } 

Hice un iterador bastante simple que toma prestado el enumerador predeterminado para hacer la mayoría (realmente todo) del trabajo. El constructor toma un IEnumerator y mi implementación simplemente le entrega el trabajo. He agregado un campo de Index a mi iterador personalizado.

Hice un ejemplo simple aquí: https://dotnetfiddle.net/0iGmVz

Para usar esta configuración de iterador, tendría algo como esto en su clase de colección / lista personalizada:

 public class MyList : List{ public new IEnumerator GetEnumerator(){ return new IndexedEnumerator(base.GetEnumerator()); } } 

Ahora foreach y otras funciones integradas obtendrán su enumerador personalizado, y cualquier comportamiento que no desee anular usará la implementación normal.

 public static class Helpers{ //Extension method to get the IndexEnumerator public static IndexedEnumerator GetIndexedEnumerator(this IEnumerable list){ return new IndexedEnumerator(list.GetEnumerator()); } } //base Enumerator methods/implementation public class BaseEnumerator : IEnumerator{ public BaseEnumerator(IEnumerator enumer){ enumerator = enumer; } protected virtual IEnumerator enumerator{get;set;} protected virtual T current {get;set;} public virtual bool MoveNext(){ return enumerator.MoveNext(); } public virtual IEnumerator GetEnumerator(){ return enumerator; } public virtual T Current {get{return enumerator.Current;}} object IEnumerator.Current {get{return enumerator.Current;}} public virtual void Reset(){} public virtual void Dispose(){} } public class IndexedEnumerator : BaseEnumerator { public IndexedEnumerator(IEnumerator enumer):base(enumer){} public int Index {get; private set;} public override bool MoveNext(){ Index++; return enumerator.MoveNext(); } }