我想创建一个IEnumerator<T>
的副本,以便我可以从集合中的特定位置重新启动枚举过程。显然,对于实现IList
的集合,这样做没有任何好处,因为我们可以记住感兴趣的索引。
使用yield
语句和Linq函数的组合是否有一种聪明的方法来完成此任务?我找不到合适的Clone()
方法来复制枚举器,并且希望避免使用Enumerable.Skip()
将新的枚举器重新定位到所需的恢复点。
此外,我希望尽可能保持解决方案的通用性,而不必依赖任何具体集合中的状态。
答案 0 :(得分:4)
你能做的最好的事情就是写一些保留缓冲区(可能是Queue<T>
)从一个而不是另一个所消耗的数据(如果你将一个迭代器提升到1M的位置会变得混乱/昂贵,但是另一个人离开了。我真的认为你最好不要重新考虑设计,而只是使用GetEnumerator()
(即另一个foreach
)重新开始 - 或缓冲数据(如果短)在列表/数组/中。
没有任何优雅的内置。
更新:这里可能有一个有趣的替代设计是“PushLINQ”;它不是克隆迭代器,而是允许多个“事物”同时消耗相同的数据提要 。
在这个例子中(从Jon的页面中解除),我们并行计算多个聚合:
// Create the data source to watch
DataProducer<Voter> voters = new DataProducer<Voter>();
// Add the aggregators
IFuture<int> total = voters.Count();
IFuture<int> adults = voters.Count(voter => voter.Age >= 18);
IFuture<int> children = voters.Where(voter => voter.Age < 18).Count();
IFuture<int> youngest = voters.Min(voter => voter.Age);
IFuture<int> 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);
答案 1 :(得分:3)
没有通用的方法,因为iEnumerable可能依赖于系统状态的任意方面,这些方面无法通过Reflection或任何其他方法检测到。例如,PaperTapeReader类可能实现一个枚举器,该枚举器从磁带读取字符,直到传感器指示机器中没有磁带。这种枚举器的状态将是磁带的物理位置,这可能无法以编程方式恢复。
给定一个iEnumerable,可以生成两个或更多个iEnumebles,每个iEnumerables都可以像原始或克隆一样。对“最远的那个”的MoveNext请求将从原始iEnumerable中读取新数据并将其缓冲为其他数据。但是,除非原始的iEnumerable支持这种“钩子”功能,否则我认为没有任何方法可以锁定其数据。
答案 2 :(得分:2)
这完全是不是的答案,但我觉得有趣的思想实验......如果你有一个基于产量的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<int>;
// 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<FieldInfo, object>();
var publicMap = new Dictionary<FieldInfo, object>();
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<int> Foo()
{
for (int i = 0; i < 10; i++)
{
yield return i;
}
yield break;
}
}
答案 3 :(得分:1)
您是希望能够保存状态,继续枚举,然后返回已保存状态,还是只想简单地枚举,执行其他操作,然后继续枚举?
如果是后者,可能会有以下内容:
public class SaveableEnumerable<T> : IEnumerable<T>, IDisposable
{
public class SaveableEnumerator : IEnumerator<T>
{
private IEnumerator<T> enumerator;
internal SaveableEnumerator(IEnumerator<T> 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<T> enumerable)
{
this.enumerator = new SaveableEnumerator(enumerable.GetEnumerator());
}
public IEnumerator<T> GetEnumerator()
{
return enumerator;
}
IEnumerator IEnumerable.GetEnumerator()
{
return enumerator;
}
public void Dispose()
{
enumerator.ActuallyDispose();
}
}
现在你可以做到:
using (IEnumerable<int> counter = new SaveableEnumerable<int>(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;
}
}
}
答案 4 :(得分:1)
JerKimball有一个有趣的方法。我试着将它提升到一个新的水平。这使用反射创建新实例,然后在新实例上设置值。我也从深度的C#中发现这一章非常有用。 Iterator block implementation details: auto-generated state machines
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<int> CountingEnumerator()
{
int i = 1;
while (true)
{
yield return i;
i++;
}
}
}
public static class EnumeratorCloner
{
public static T Clone<T>(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;
}
}
答案 5 :(得分:0)
所以真正想要的是能够稍后恢复迭代,我是否正确?克隆调查员或集合是你认为你做这样的事情吗?
你可以创建一个包装IEnumerable的类,并公开一个自定义枚举器,它在内部克隆内部IEnumerable,然后枚举它。然后,使用GetEnumerator()将为您提供一个可以传递的枚举器。
这将为“飞行中”的每个枚举器创建一个额外的IEnumerable副本,但我认为它可以满足您的需求。