Я хотел бы создать копию IEnumerator<T>
так, чтобы я мог перезапустить процесс перечисления от конкретного местоположения в наборе. Очевидно, нет никакого преимущества для выполнения так для наборов та реализация IList
, так как мы можем помнить индекс интереса.
Есть ли умный способ выполнить эту задачу с помощью комбинации yield
операторы и функции Linq? Я не мог найти подходящее Clone()
метод для копирования перечислителя, и хотел бы избегать использования Enumerable.Skip()
изменить местоположение нового перечислителя к желаемой точке возобновления.
Кроме того, я хотел бы сохранить решения максимально универсальными и не иметь для зависимости от состояния от любых конкретных наборов.
Лучшее, что вы могли бы сделать, это написать что-то, что хранит буфер (возможно, Queue
) данных, потребляемых одним, а не другим (что приведет к грязно / дорого, если вы продвинули один итератор на 1 млн позиций, а другой оставили в покое). Я действительно думаю, что вам лучше было бы переосмыслить дизайн и просто использовать GetEnumerator ()
(то есть еще один foreach
), чтобы начать заново - или буферизовать данные (если короткие) в списке / массиве / чем угодно.
Ничего изящного не встроено.
Обновление: возможно, интересный альтернативный дизайн здесь - " PushLINQ "; вместо того, чтобы клонировать итератор, он позволяет нескольким «вещам» потреблять один и тот же поток данных одновременно .
В этом примере (взятом со страницы Джона) мы вычисляем несколько агрегатов параллельно:
// 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);
Вы хотите сохранить состояние, продолжить перечисление, а затем вернуться в сохраненное состояние, или вы хотите просто иметь возможность перечислять, делать что-то еще, а затем продолжить перечисление?
Если во втором случае может сработать что-то вроде следующего:
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;
}
}
}
Это полностью не ответ, но мысленный эксперимент, который я нашел интересным ... если у вас есть IEnumerable на основе yield, я полагаю, вы знаете, что это все генерируемая компилятором магия. Если у вас есть такой зверь, вы могли бы сделать что-то вроде этого ...;)
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;
}
}
Итак, что вы действительно хотите, так это иметь возможность возобновить итерацию позже, я прав? А клонирование перечислителя или коллекции - вот как вы думаете, что вы бы сделали это?
Вы могли бы создать класс, который обертывает IEnumerable и предоставляет настраиваемый перечислитель, который внутренне клонирует внутренний IEnumerable, а затем перечисляет его . Затем использование GetEnumerator () даст вам перечислитель, который можно будет передавать.
Это создаст дополнительную копию IEnumerable для каждого перечислителя «в полете», но я думаю, что это удовлетворит ваши потребности.
У JerKimball интересный подход. Я пытаюсь поднять его на новый уровень. Здесь используется отражение для создания нового экземпляра, а затем устанавливаются значения для нового экземпляра. Я также нашел эту главу из C# in Depth очень полезной. Детали реализации блока итератора: автоматически генерируемые машины состояний
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;
}
}