能够重置使用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) }