Действительно ли возможно клонировать IEnumerable <T> экземпляр, сохраняя копию итеративного состояния?

Я хотел бы создать копию IEnumerator<T> так, чтобы я мог перезапустить процесс перечисления от конкретного местоположения в наборе. Очевидно, нет никакого преимущества для выполнения так для наборов та реализация IList, так как мы можем помнить индекс интереса.

Есть ли умный способ выполнить эту задачу с помощью комбинации yield операторы и функции Linq? Я не мог найти подходящее Clone() метод для копирования перечислителя, и хотел бы избегать использования Enumerable.Skip() изменить местоположение нового перечислителя к желаемой точке возобновления.

Кроме того, я хотел бы сохранить решения максимально универсальными и не иметь для зависимости от состояния от любых конкретных наборов.

10
задан Steve Guidi 16 December 2009 в 05:26
поделиться

5 ответов

Лучшее, что вы могли бы сделать, это написать что-то, что хранит буфер (возможно, 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);
4
ответ дан 4 December 2019 в 01:01
поделиться

Вы хотите сохранить состояние, продолжить перечисление, а затем вернуться в сохраненное состояние, или вы хотите просто иметь возможность перечислять, делать что-то еще, а затем продолжить перечисление?

Если во втором случае может сработать что-то вроде следующего:

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;
        }
    }
}
1
ответ дан 4 December 2019 в 01:01
поделиться

Это полностью не ответ, но мысленный эксперимент, который я нашел интересным ... если у вас есть 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;
    }

}
2
ответ дан 4 December 2019 в 01:01
поделиться

Итак, что вы действительно хотите, так это иметь возможность возобновить итерацию позже, я прав? А клонирование перечислителя или коллекции - вот как вы думаете, что вы бы сделали это?

Вы могли бы создать класс, который обертывает IEnumerable и предоставляет настраиваемый перечислитель, который внутренне клонирует внутренний IEnumerable, а затем перечисляет его . Затем использование GetEnumerator () даст вам перечислитель, который можно будет передавать.

Это создаст дополнительную копию IEnumerable для каждого перечислителя «в полете», но я думаю, что это удовлетворит ваши потребности.

0
ответ дан 4 December 2019 в 01:01
поделиться

У 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;
    }
}
1
ответ дан 4 December 2019 в 01:01
поделиться
Другие вопросы по тегам:

Похожие вопросы: