Ability to reset IEnumerator generated using yield (C#)
There is no built-in support, but you can define your own implementation of IEnumerator
that delegates all method calls to the enumerator generated by C# and only lets you define your own behavior for the Reset
method.
The simplest version of the class would look like this:
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(); }
}
In this case, the ResetFunc
that you specify returns a new IEnumerator<T>
, so your provided implementation of ResetFunc
can do some cleanup or whatever you need to do when resetting and then return a new enumerator.
IEnumerator<int> Foo() { /* using yield return */ }
IEnumerator<int> PublicFoo() {
return new ResetableEnumerator<int> {
Enumerator = Foo(),
ResetFunc = () => {
Cleanup();
return Foo(); } };
}
You'll need to store all the originally local variables of the Foo
method as fields of the class, so that you can access them in Cleanup
(Note that the rest of the Foo
body will never be executed after calling Reset
), but that's still easier than writing a handwritten iterator!
There's a nice workaround that I just discovered. Make your generator method return IEnumerable
, not IEnumerator
. Then you can do
var values = MyGeneratorMethod();
var enumerator = values.GetEnumerator();
// ... do stuff with enumerator
enumerator = values.GetEnumerator(); // instead of enumerator.Reset();
I believe itowlson's answer suggested this exact trick, but I couldn't understand it until I heard of the trick elsewhere.
No, it is not possible. When the C# compiler processes an iterator (a method that contains a yield
statement), the compiler generates a class that implements IEnumerable and IEnumerator. The generated class' implementation of Reset just throws a NotSupportedException. There is no way to influence this in current versions of C#.
Instead, your calling code will need to request a new enumerator, i.e. begin a new foreach loop. Or you will need to forgo the language support (the yield
statement) and write your own class which implements IEnumerator.