使用迭代器编写自定义IEnumerator

我如何编写一个自定义的IEnumerator实现,它需要维护一些状态并仍然可以使用迭代器块来简化它? 我能想到的最好的是这样的:

 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(); } ... } 

为什么要编写迭代器类? 迭代器块的重点是你不必……

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

如果你添加更多的上下文(即,为什么以上不能工作),我们可能会提供更多帮助。

但是如果可能的话,避免编写迭代器类。 他们工作很多,容易出错。

顺便说一下,你真的不必费心去Reset – 它基本上已被弃用,并且不应该被使用(因为它不能被用于任意枚举器)。

如果你想使用内部迭代器,那也没关系:

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

或者如果您只有一个枚举器:

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

您也可以考虑将状态(作为元组)添加到您收益的事物中:

 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); 

最后,您可以使用LINQ样式的扩展/委托方法,使用Action为调用者提供位置和值:

  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); } } 

输出:

 0: a 1: b 2: c 

我必须在这里同意Marc。 如果你真的想要自己写一个枚举器类(只是因为你可以?)或者只是使用一个interator块和yield语句并完成它。 就个人而言,我再也不会接触调查员课程了。 😉

@Marc Gravell

但是如果可能的话,避免编写迭代器类。 他们工作很多,容易出错。

这正是我想在迭代器中使用yield机械来完成繁重工作的原因。

您也可以考虑将状态(作为元组)添加到您收益的事物中:

是的,这很有效。 但是,这是每一步的额外分配。 如果我在大多数步骤中只对T感兴趣,那么如果我可以避免它,那就是我不需要的开销。

但是,你的最后一个建议给了我一个想法:

 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()); } 

我做了一个非常简单的迭代器,它借用了默认的Enumerator来完成大部分(全部)工作。 构造函数采用IEnumerator ,我的实现只是简单地交给它工作。 我在自定义迭代器中添加了一个Index字段。

我在这里做了一个简单的例子: https : //dotnetfiddle.net/0iGmVz

要使用此Iterator设置,您可以在自定义Collection / List类中使用以下内容:

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

现在foreach和其他内置函数将获得您的自定义枚举器,并且您不想覆盖的任何行为将使用正常实现。

 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(); } }