能够重置使用yield生成的IEnumerator(C#)

Cas*_*ash 18 c#

如果我使用yield而不是手动创建IEnumerator,是否可以实现IEnumerator.Reset?

ito*_*son 20

不,这是不可能的.当C#编译器处理迭代器(包含yield语句的方法)时,编译器会生成一个实现IEnumerable和IEnumerator的类.生成的类的Reset实现只会抛出NotSupportedException.在当前版本的C#中无法影响这一点.

相反,您的调用代码将需要请求一个新的枚举器,即开始一个新的foreach循环.或者您需要放弃语言支持(yield语句)并编写自己的实现IEnumerator的类.


Tom*_*cek 10

没有内置支持,但您可以定义自己的实现IEnumerator,委托对C#生成的枚举器的所有方法调用,并且只允许您为Reset方法定义自己的行为.

该类的最简单版本如下所示:

class ResetableEnumerator<T> : IEnumerator<T>
{
  public IEnumerator<T> Enumerator { get; set; }
  public Func<IEnumerator<T>> 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(); }
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,ResetFunc您指定的返回一个new IEnumerator<T>,因此您提供的实现ResetFunc可以执行一些清理或重置时需要执行的任何操作,然后返回一个新的枚举器.

IEnumerator<int> Foo() { /* using yield return */ }
IEnumerator<int> PublicFoo() {
  return new ResetableEnumerator<int> { 
    Enumerator = Foo(),
    ResetFunc = () => { 
      Cleanup();
      return Foo(); } };
}
Run Code Online (Sandbox Code Playgroud)

您需要将方法的所有原始局部变量存储Foo为类的字段,以便您可以访问它们Cleanup(注意,Foo在调用之后将永远不会执行正文的其余部分Reset),但这仍然比编写手写的迭代器!


Nev*_*ind 6

我刚刚发现了一个很好的解决方法。让您的生成器方法返回IEnumerable而不是 IEnumerator。然后你可以做

var values = MyGeneratorMethod();
var enumerator = values.GetEnumerator();
// ... do stuff with enumerator
enumerator = values.GetEnumerator(); // instead of enumerator.Reset();
Run Code Online (Sandbox Code Playgroud)

我相信 itowlson 的回答暗示了这个确切的技巧,但直到我在其他地方听说过这个技巧我才理解它。