能够重置使用yield生成的IEnumerator(C#)
如果我使用yield而不是手动创建IEnumerator,是否可以实现IEnumerator.Reset?
没有内置支持,但是您可以定义自己的IEnumerator
实现,它将所有方法调用委托给C#生成的枚举器,并且只允许您为Reset
方法定义自己的行为。
该类的最简单版本如下所示:
class ResetableEnumerator : IEnumerator { public IEnumerator Enumerator { get; set; } public Func> ResetFunc { get; set; } public T Current { get { return Enumerator.Current; } } public void Dispose() { Enumerator.Dispose(); } object IEnumerator.Current { get { return Current; } } public bool MoveNext() { return Enumerator.MoveNext(); } public void Reset() { Enumerator = ResetFunc(); } }
在这种情况下,您指定的ResetFunc
将返回一个新的IEnumerator
,因此您提供的ResetFunc
实现可以执行一些清理或重置时需要执行的任何操作,然后返回一个新的枚举器。
IEnumerator Foo() { /* using yield return */ } IEnumerator PublicFoo() { return new ResetableEnumerator { Enumerator = Foo(), ResetFunc = () => { Cleanup(); return Foo(); } }; }
您需要将Foo
方法的所有原始局部变量存储为类的字段,以便您可以在Cleanup
访问它们(请注意,在调用Reset
之后永远不会执行Foo
主体的其余部分),但这仍然是比编写手写迭代器更容易!
不,这是不可能的。 当C#编译器处理迭代器(包含yield
语句的方法)时,编译器会生成一个实现IEnumerable和IEnumerator的类。 生成的类的Reset实现只会抛出NotSupportedException。 在当前版本的C#中无法影响这一点。
相反,您的调用代码将需要请求一个新的枚举器,即开始一个新的foreach循环。 或者您将需要放弃语言支持( yield
语句)并编写自己的实现IEnumerator的类。
我刚刚发现了一个很好的解决方法。 让你的生成器方法返回IEnumerable
, 而不是 IEnumerator
。 那你可以做
var values = MyGeneratorMethod(); var enumerator = values.GetEnumerator(); // ... do stuff with enumerator enumerator = values.GetEnumerator(); // instead of enumerator.Reset();
我相信itowlson的答案提出了这个确切的伎俩,但在我听到其他地方的伎俩之前我无法理解。
您可以添加逻辑,当满足时,通过发出yield break;
重置yield break;
退出迭代器块而不返回任何更多元素。 例如:
public IEnumerable GetLimitedIntegers(int limit) { foreach (int i in Enumerable.Range(1, 10)) { if (i < limit) yield return i; else yield break; } }
然后使用它:
foreach(int i in GetLimitedIntegers(6)) Console.WriteLine(i);
此代码实现了一个可以设置为重置枚举器的属性。 笔记:
- 不幸的是,这种重置方法不适用于多个枚举器实例
- 这不会通过界面重置它,但实际上是通过直接或通过方法设置属性
- 鉴于复杂性和脆弱性,我实际上不会推荐这种方法
- 我其实想知道goto是否可能更整洁
码:
public IEnumerator GetEnumerator(){ do{ this.shouldReset=FALSE; for (Entry e = this.ReadEntry(); e != null; e = this.ReadEntry()){ if(self.shouldReset)break; else yield return e; } } while(self.shouldReset) }