是否可以克隆IEnumerable 实例,保存迭代状态的副本?

我想创建一个IEnumerator的副本,以便我可以从集合中的特定位置重新启动枚举过程。 显然,对于实现IList集合,这样做没有任何好处,因为我们可以记住感兴趣的索引。

使用yield语句和Linq函数的组合是否有一种聪明的方法来完成此任务? 我找不到合适的Clone()方法来复制枚举器,并且希望避免使用Enumerable.Skip()将新的枚举器重新定位到所需的恢复点。

此外,我希望尽可能保持解决方案的通用性,而不必依赖任何具体集合中的状态。

你能做的最好的事情就是写一些东西来保存一个缓冲区(可能是一个Queue )而不是另一个(如果你将一个迭代器提前1M个位置,那将会变得混乱/昂贵)单独)。 我真的认为你最好不要重新考虑设计,只需使用GetEnumerator() (即另一个foreach )重新开始 – 或者在列表/数组/缓存中缓冲数据(如果短)。

没有什么优雅的内置。


更新:也许这里有一个有趣的替代设计是“ PushLINQ ”; 它不是克隆迭代器,而是允许多个“事物”同时使用相同的数据馈送。

在这个例子中(从Jon的页面中解除),我们并行计算多个聚合:

 // Create the data source to watch DataProducer voters = new DataProducer(); // Add the aggregators IFuture total = voters.Count(); IFuture adults = voters.Count(voter => voter.Age >= 18); IFuture children = voters.Where(voter => voter.Age < 18).Count(); IFuture youngest = voters.Min(voter => voter.Age); IFuture oldest = voters.Select(voter => voter.Age).Max(); // Push all the data through voters.ProduceAndEnd(Voter.AllVoters()); // Write out the results Console.WriteLine("Total voters: {0}", total.Value); Console.WriteLine("Adult voters: {0}", adults.Value); Console.WriteLine("Child voters: {0}", children.Value); Console.WriteLine("Youngest vote age: {0}", youngest.Value); Console.WriteLine("Oldest voter age: {0}", oldest.Value); 

没有一般的方法可以做到这一点,因为iEnumerable可能依赖于系统状态的任意方面,这些方面无法通过Reflection或任何其他方式检测到。 例如,PaperTapeReader类可能实现一个枚举器,该枚举器从磁带读取字符,直到传感器指示机器中没有磁带。 这种枚举器的状态将是磁带的物理位置,这可能无法以编程方式恢复。

给定一个iEnumerable,就有可能生成两个或更多个iEnumebles,每个iEnumerables都可以像原始版本或克隆版本一样。 MoveNext对“最远”的请求将从原始iEnumerable中读取新数据并将其缓冲为其他数据。 但是,除非原始的iEnumerable支持这种“钩子”function,否则我认为没有任何方法可以锁定其数据。

这完全不是一个答案,但我觉得有趣的思想实验……如果你有一个基于产量的IEnumerable,我想你知道它是所有编译器生成的魔法。 如果你有这样的野兽,你可以这样做……;)

 class Program { static void Main(string[] args) { var bar = new Program().Foo(); // Get a hook to the underlying compiler generated class var barType = bar.GetType().UnderlyingSystemType; var barCtor = barType.GetConstructor(new Type[] {typeof (Int32)}); var res = barCtor.Invoke(new object[] {-2}) as IEnumerable; // Get our enumerator var resEnum = res.GetEnumerator(); resEnum.MoveNext(); resEnum.MoveNext(); Debug.Assert(resEnum.Current == 1); // Extract and save our state var nonPublicMap = new Dictionary(); var publicMap = new Dictionary(); var nonpublicfields = resEnum.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance); var publicfields = resEnum.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance); foreach(var field in nonpublicfields) { var value = field.GetValue(resEnum); nonPublicMap[field] = value; } foreach (var field in publicfields) { var value = field.GetValue(resEnum); publicMap[field] = value; } // Move about resEnum.MoveNext(); resEnum.MoveNext(); resEnum.MoveNext(); resEnum.MoveNext(); Debug.Assert(resEnum.Current == 5); // Restore state foreach (var kvp in nonPublicMap) { kvp.Key.SetValue(resEnum, kvp.Value); } foreach (var kvp in publicMap) { kvp.Key.SetValue(resEnum, kvp.Value); } // Move about resEnum.MoveNext(); resEnum.MoveNext(); Debug.Assert(resEnum.Current == 3); } public IEnumerable Foo() { for (int i = 0; i < 10; i++) { yield return i; } yield break; } } 

你想要能够保存状态,继续枚举,然后返回到保存的状态,或者你想简单地能够枚举,做一些其他的东西,然后继续枚举?

如果是后者,可以使用以下内容:

 public class SaveableEnumerable : IEnumerable, IDisposable { public class SaveableEnumerator : IEnumerator { private IEnumerator enumerator; internal SaveableEnumerator(IEnumerator enumerator) { this.enumerator = enumerator; } public void Dispose() { } internal void ActuallyDispose() { enumerator.Dispose(); } public bool MoveNext() { return enumerator.MoveNext(); } public void Reset() { enumerator.Reset(); } public T Current { get { return enumerator.Current; } } object IEnumerator.Current { get { return enumerator.Current; } } } private SaveableEnumerator enumerator; public SaveableEnumerable(IEnumerable enumerable) { this.enumerator = new SaveableEnumerator(enumerable.GetEnumerator()); } public IEnumerator GetEnumerator() { return enumerator; } IEnumerator IEnumerable.GetEnumerator() { return enumerator; } public void Dispose() { enumerator.ActuallyDispose(); } } 

现在你可以这样做:

 using (IEnumerable counter = new SaveableEnumerable(CountableEnumerable())) { foreach (int i in counter) { Console.WriteLine(i); if (i > 10) { break; } } DoSomeStuff(); foreach (int i in counter) { Console.WriteLine(i); if (i > 20) { break; } } } 

JerKimball有一个有趣的方法。 我试着把它提升到一个新的水平。 这使用reflection创建新实例,然后在新实例上设置值。 我也从深度的C#中发现这一章非常有用。 迭代器块实现细节:自动生成的状态机

 static void Main() { var counter = new CountingClass(); var firstIterator = counter.CountingEnumerator(); Console.WriteLine("First list"); firstIterator.MoveNext(); Console.WriteLine(firstIterator.Current); Console.WriteLine("First list cloned"); var secondIterator = EnumeratorCloner.Clone(firstIterator); Console.WriteLine("Second list"); secondIterator.MoveNext(); Console.WriteLine(secondIterator.Current); secondIterator.MoveNext(); Console.WriteLine(secondIterator.Current); secondIterator.MoveNext(); Console.WriteLine(secondIterator.Current); Console.WriteLine("First list"); firstIterator.MoveNext(); Console.WriteLine(firstIterator.Current); firstIterator.MoveNext(); Console.WriteLine(firstIterator.Current); } public class CountingClass { public IEnumerator CountingEnumerator() { int i = 1; while (true) { yield return i; i++; } } } public static class EnumeratorCloner { public static T Clone(T source) where T : class, IEnumerator { var sourceType = source.GetType().UnderlyingSystemType; var sourceTypeConstructor = sourceType.GetConstructor(new Type[] { typeof(Int32) }); var newInstance = sourceTypeConstructor.Invoke(new object[] { -2 }) as T; var nonPublicFields = source.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance); var publicFields = source.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance); foreach (var field in nonPublicFields) { var value = field.GetValue(source); field.SetValue(newInstance, value); } foreach (var field in publicFields) { var value = field.GetValue(source); field.SetValue(newInstance, value); } return newInstance; } } 

所以你真正想要的是能够在以后恢复迭代,我是否正确? 克隆调查员或集合是你认为你做这样的事情?

你可以创建一个包装IEnumerable的类,并公开一个自定义枚举器,它在内部克隆内部IEnumerable,然后枚举它。 然后,使用GetEnumerator()将为您提供一个可以传递的枚举器。

这将为“飞行中”的每个枚举器创建一个额外的IEnumerable副本,但我认为它可以满足您的需求。