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