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

能够重置使用产量(C#)生成的IEnumerator,c#,C#,如果我使用yield而不是手动创建IEnumerator,是否可以实现IEnumerator.Reset?此代码实现了一个可以设置为重置枚举数的属性。注: 不幸的是,这种重置方法不适用于多个枚举数实例 这并不是通过接口重置它,而是通过直接或通过方法设置属性来重置它 考虑到复杂性和脆弱性,我实际上不推荐这种方法 我真的在想,去厕所是否更整洁 代码: public IEnumerator GetEnumerator(){ 做{ 此.shouldReset=FALSE; for(条目e=this.

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

此代码实现了一个可以设置为重置枚举数的属性。注:

  • 不幸的是,这种重置方法不适用于多个枚举数实例
  • 这并不是通过接口重置它,而是通过直接或通过方法设置属性来重置它
  • 考虑到复杂性和脆弱性,我实际上不推荐这种方法
  • 我真的在想,去厕所是否更整洁
代码:

public IEnumerator GetEnumerator(){
做{
此.shouldReset=FALSE;
for(条目e=this.ReadEntry();e!=null;e=this.ReadEntry()){
如果(自身应复位)中断;
否则收益率e;
}
}
while(self.shouldReset)
}

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


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

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

IEnumerator<int> Foo() { /* using yield return */ }
IEnumerator<int> PublicFoo() {
  return new ResetableEnumerator<int> { 
    Enumerator = Foo(),
    ResetFunc = () => { 
      Cleanup();
      return Foo(); } };
}
该类的最简单版本如下所示:

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

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

我刚刚发现了一个很好的解决方法。使生成器方法返回
IEnumerable
而不是
IEnumerator
。那你就可以了

var values = MyGeneratorMethod();
var enumerator = values.GetEnumerator();
// ... do stuff with enumerator
enumerator = values.GetEnumerator(); // instead of enumerator.Reset();

我相信伊托尔森的回答暗示了这个确切的诀窍,但直到我在别处听说这个诀窍,我才理解它。

我解决这个问题的方法是使用一个包装类,它可以包装任何屈服函数并重置:

public class Resettable : IEnumerator
{
  private IEnumerator inner;
  private Func<IEnumerator> func;

  public Resettable(Func<IEnumerator> func)
  {
    this.func = func;
    inner = func.Invoke();
  }

  public bool MoveNext()
  {
    return inner.MoveNext();
  }

  public object Current { get { return inner.Current; } }

  public void Reset()
  {
    inner = func.Invoke();
  }

  public static Resettable Make(Func<IEnumerator> func)
  {
    return new Resettable(func);
  }

  public static Resettable Make<T1>(Func<T1, IEnumerator> func, T1 a1)
  {
    Func<IEnumerator> capture = () => { return func.Invoke(a1); };
    return new Resettable(capture);
  }

  public static Resettable Make<T1, T2>(Func<T1, T2, IEnumerator> func, T1 a1, T2 a2)
  {
    Func<IEnumerator> capture = () => { return func.Invoke(a1, a2); };
    return new Resettable(capture);
  }

  public static Resettable Make<T1, T2, T3>(Func<T1, T2, T3, IEnumerator> func, T1 a1, T2 a2, T3 a3)
  {
    Func<IEnumerator> capture = () => { return func.Invoke(a1, a2, a3); };
    return new Resettable(capture);
  }

  // add more of these to support more arguments
}
输出:

value: 5
value: 6
value: 7
Reset
value: 5
value: 6
value: 7

你能详细说明一下你期望这个“重置”会做什么吗?如果调用方正在执行“foreach”语句的一半,并对您的结果进行迭代,会发生什么情况?@Matt:我想实现标准枚举数重置方法:是的,但IEnumerator.reset与“yield return”并不完全一致。您正在向正在进行的枚举器提供值-从您的一端重置它是没有意义的。调用代码负责重置。@马特:我知道调用代码应该重置它。因此我想实现这个方法!IEnumerator.Reset不是由大多数.NET枚举器实现的:CLR文档明确声明,“Reset方法是为COM互操作性提供的。它不一定需要实现;相反,实现者可以简单地抛出NotSupportedException。”因此,除非您与COM代码对话,否则可能不值得费心。你能说说你的用例吗?@itowlson:修复了错误。这实际上并没有实现该方法,只是提供了一个带有限制的粗略解决方案。不幸的是,我的代码不再正确显示:-(看起来有一个bug将代码块直接放在项目符号列表之后。我已经为您详细介绍了它*grin*。请注意,实现IEnumerator的自定义类在内部可能使用“产生返回”的方法。
public class Program
{ 
  static IEnumerator YieldTest(int start)
  {
    yield return start;
    yield return start + 1;
    yield return start + 2;
  }
    
  public static void Main()
  {
    // IEnumerator en = YieldTest(5);
    IEnumerator en = Resettable.Make(YieldTest, 5);

    while(en.MoveNext())
    {
      Console.WriteLine("value: "+en.Current.ToString());
    }
    Console.WriteLine("Reset");
    en.Reset();
    while(en.MoveNext())
    {
      Console.WriteLine("value: "+en.Current.ToString());
    }
  }
}
value: 5
value: 6
value: 7
Reset
value: 5
value: 6
value: 7