Запись пользовательского IEnumerator <T> с помощью итераторов
Как я могу написать пользовательскую реализацию IEnumerator<T>
, которая должна поддерживать какое-то состояние и все-таки использовать блоки итератора для ее упрощения? Лучшее, что я могу придумать, это что-то вроде этого:
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>();
}
...
}
Ответы
Ответ 1
Почему вы хотите написать класс итератора? Вся точка блока итератора такова, что вам не нужно...
то есть.
public IEnumerator<T> GetEnumerator() {
int position = 0; // state
while(whatever) {
position++;
yield return ...something...;
}
}
Если вы добавляете больше контекста (i, e, почему это не работает), мы, возможно, можем больше помочь.
Но если возможно, избегайте писать класс итератора. Они много работают и легко ошибаются.
Кстати, вам действительно не нужно беспокоиться о Reset
- он в значительной степени устарел и на самом деле никогда не должен использоваться (так как нельзя использовать работу для произвольного перечислителя).
Если вы хотите использовать внутренний итератор, это тоже хорошо:
int position = 0;
foreach(var item in source) {
position++;
yield return position;
}
или если у вас есть только счетчик:
while(iter.MoveNext()) {
position++;
yield return iter.Current;
}
Вы также можете подумать о добавлении состояния (как кортежа) к тому, что вы даете:
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);
Наконец, вы можете использовать подход расширения/делегата в стиле LINQ с Action<int,T>
для предоставления позиции и значения вызывающему абоненту:
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);
}
}
Выходы:
0: a
1: b
2: c
Ответ 2
Мне нужно согласиться с Марком здесь. Либо напишите класс перечисления полностью самостоятельно, если вы действительно хотите (просто потому, что можете?), Или просто использовать блок-интерпретатор и вывести утверждения и сделать с ним. Лично я больше не трогаю классы перечислителей.; -)
Ответ 3
@Marc Gravell
Но если возможно, избегайте писать класс итератора. Они много работают и легко ошибаются.
Именно поэтому я хочу использовать механизмы yield
внутри моего итератора, чтобы сделать тяжелый подъем.
Вы также можете подумать о добавлении состояния (как кортежа) к тому, что вы даете:
Да, это работает. Тем не менее, это дополнительное распределение на каждом шагу. Если меня интересует только T
на большинстве шагов, то накладные расходы мне не нужны, если я могу избежать этого.
Однако последнее предложение дало мне представление:
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>());
}
Ответ 4
Я сделал довольно простой Iterator, который заставляет Enumerator по умолчанию выполнять большинство (действительно все) работы. Конструктор принимает IEnumerator<T>
, и моя реализация просто передает ему работу. Я добавил поле Index
в свой пользовательский Iterator.
Я сделал простой пример здесь: https://dotnetfiddle.net/0iGmVz
Чтобы использовать эту настройку Iterator, у вас есть что-то подобное в вашем обычном классе Collection/List:
public class MyList<T> : List<T>{
public new IEnumerator<T> GetEnumerator(){
return new IndexedEnumerator<T>(base.GetEnumerator());
}
}
Теперь foreach
и другие встроенные функции получат ваш пользовательский перечислитель, и любое поведение, которое вы не хотите переопределять, будет использовать обычную реализацию.
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();
}
}