ienumerable c#
Escribir IEnumerator personalizado<T> con iteradores (4)
¿Por qué quieres escribir una clase de iterador? El objetivo de un bloque iterador es que no tenga que ...
es decir
public IEnumerator<T> 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 ayudarlo más.
Pero si es posible, evite escribir una clase de iterador. Son mucho trabajo y es fácil equivocarse.
Por cierto, realmente no tienes que molestarte con Reset
: está en gran parte desaprobado, y en realidad no debería usarse nunca (ya que no se puede confiar en que funcione para un enumerador arbitrario).
Si quieres consumir un iterador interno, 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 la cosa que cede:
class MyState<T> {
public int Position {get;private set;}
public T Current {get;private set;}
public MyState(int position, T current) {...} // assign
}
...
yield return new MyState<Foo>(position, item);
Finalmente, podría usar un enfoque de extensión / delegado al estilo LINQ, con una Action<int,T>
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<T>(
this IEnumerable<T> source,
Action<int, T> 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);
}
}
Productos:
0: a
1: b
2: c
¿Cómo puedo escribir una IEnumerator<T>
personalizada de IEnumerator<T>
que necesite mantener algún estado y seguir usando bloques de iteradores para simplificarlo? Lo mejor que se me ocurre es algo como esto:
public class MyEnumerator<T> : IEnumerator<T> {
private IEnumerator<T> _enumerator;
public int Position {get; private set;} // or some other custom properties
public MyEnumerator() {
Position = 0;
_enumerator = MakeEnumerator();
}
private IEnumerator<T> 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<T> : IEnumerable<T> {
IEnumerator<T> IEnumerable<T>.GetEnumerator() {
return GetEnumerator();
}
public MyEnumerator<T> GetEnumerator() {
return new MyEnumerator<T>();
}
...
}
@Marc Gravell
Pero si es posible, evite escribir una clase de iterador. Son mucho trabajo y es fácil equivocarse.
Esa es precisamente la razón por la 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 la cosa que cede:
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 una sobrecarga que no necesito si puedo evitarlo.
Sin embargo, tu última sugerencia me dio una idea:
public IEnumerator<T> GetEnumerator(Action<T, int> action) {
int position = 0; // state
while(whatever) {
position++;
var t = ...something...;
action(t, position);
yield return t;
}
}
public IEnumerator<T> GetEnumerator() {
return GetEnumerator(DoNothing<T, int>());
}
Tendría que estar de acuerdo con Marc aquí. Escriba usted mismo una clase de enumerador usted mismo si realmente quiere (¿solo porque puede?) O simplemente use un bloque de interator y rinda declaraciones y termine con él. Personalmente, nunca volveré a tocar clases de enumerador. ;-)
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<T>
y mi implementación simplemente le da 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 personalizada de Colección / Lista:
public class MyList<T> : List<T>{
public new IEnumerator<T> GetEnumerator(){
return new IndexedEnumerator<T>(base.GetEnumerator());
}
}
Ahora foreach
y otras funciones integradas obtendrán su enumerador personalizado, y cualquier comportamiento que no desee anular utilizará la implementación normal.
public static class Helpers{
//Extension method to get the IndexEnumerator
public static IndexedEnumerator<T> GetIndexedEnumerator<T>(this IEnumerable<T> list){
return new IndexedEnumerator<T>(list.GetEnumerator());
}
}
//base Enumerator methods/implementation
public class BaseEnumerator<T> : IEnumerator<T>{
public BaseEnumerator(IEnumerator<T> enumer){
enumerator = enumer;
}
protected virtual IEnumerator<T> enumerator{get;set;}
protected virtual T current {get;set;}
public virtual bool MoveNext(){
return enumerator.MoveNext();
}
public virtual IEnumerator<T> 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<T> : BaseEnumerator<T>
{
public IndexedEnumerator(IEnumerator<T> enumer):base(enumer){}
public int Index {get; private set;}
public override bool MoveNext(){
Index++;
return enumerator.MoveNext();
}
}