IEnumerable <T>представляет «остаток» последовательности IEnumerable <T> - PullRequest
3 голосов
/ 14 мая 2010

Если я иду через IEnumerable<T>, есть ли способ получить новый IEnumerable<T>, представляющий остальные предметы после текущего.

Например, я хотел бы написать метод расширения IEnumerator<T>.Remaining():

IEnumerable<int> sequence = ...
IEnumerator<int> enumerator = sequence.GetEnumerator();

if (enumerator.MoveNext() && enumerator.MoveNext()) {
    IEnumerable<int> rest = enumerator.Remaining();
    // 'rest' would contain elements in 'sequence' start at the 3rd element
}

Я думаю о коллекции своего рода односвязного списка, так что должен быть способ представить любые оставшиеся элементы, верно? Я не вижу способа сделать это доступным для IEnumerable<T> или IEnumerator<T>, поэтому, возможно, это несовместимо с понятием потенциально неограниченной, недетерминированной последовательности элементов.

Ответы [ 4 ]

3 голосов
/ 14 мая 2010

Если вы должны использовать IEnumerator<T>, а не IEnumerable<T> (где все хорошие методы расширения), вот два простых метода.

Этот может быть перечислен только один раз (и привязан к исходному перечисляемому, что означает, что вы можете получить исключения, если другой поток изменит список источников):

public static IEnumerable<T> Remaining<T>( this IEnumerator<T> value ) {
    while( value.MoveNext() ) {
        yield return value.Current;
    }
}

И этот создает список, и его можно многократно перечислять (и он отключен от исходного перечислителя, поэтому вам не нужно беспокоиться об изменении исходного IEnumerable):

public static IEnumerable<T> Remaining<T>( this IEnumerator<T> value ) {
    List<T> list = new List<T>();
    while( value.MoveNext() ) list.Add( value.Current );

    return list;
}
2 голосов
/ 14 мая 2010

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

Причина этого в том, что в общем смысле перечисляемое может быть перечислено несколько раз, тогда как перечислитель не может, это просто один из этих "многократных" сам по себе.

Во-первых, вы можете попытаться выяснить, с какой коллекцией вы имеете дело, и таким образом вернуть соответствующий перечислитель поверх остального исходного перечислителя. Причина, по которой ты идешь.

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

Или ... вы можете сделать то, что предложили несколько, на самом деле не возвращать перечислитель, а вместо этого использовать методы Skip и Take класса enumerable, чтобы вернуть то, что вы хотите. Это вернет новое перечисляемое значение, которое при каждом перечислении будет перечислять исходное перечисляемое, пропустить первые два элемента и произвести остальные.

Позвольте мне перефразировать последний абзац. Если вы не попытаетесь вернуть остальную часть IEnumerator<T> в качестве нового перечислимого, а вместо этого будете иметь дело только с исходной коллекцией, с ней станет намного легче иметь дело.

Вот некоторый код, который кэширует элементы. Преимущество состоит в том, что если вы создадите 2 или более перечислителей (или даже просто 1) из полученного перечисляемого, а затем позволите перечислимому выйти из области видимости, поскольку перечислители начнут перемещаться по элементам, это позволит запустить сборщик мусора собирая элементы, которые прошли мимо.

Другими словами, если вы сделаете это:

var enumerable = enumerator.Remaining();
var enumerator1 = enumerable.GetEnumerator();
var enumerator2 = enumerable.GetEnumerator();

enumerator1.MoveNext();
enumerator2.MoveNext();
<-- at this point, enumerable is no longer used, and the first (head) element
    of the enumerable is no longer needed (there's no way to get to it)
    it can be garbage collected.

Конечно, если вы будете хранить перечислимый объект и перечислять все элементы в нем, он создаст в памяти копию всех элементов из исходного перечисляемого элемента, что, как я уже сказал, может быть дорогостоящим.

В любом случае, вот код. Это не потокобезопасно:

using System;
using System.Collections.Generic;
using System.Collections;

namespace SO2829956
{
    public class EnumeratorEnumerable<T> : IEnumerable<T>
    {
        private class Node
        {
            public T Value;
            public Node Next;
        }

        private class Enumerator : IEnumerator<T>
        {
            private IEnumerator<T> _Enumerator;
            private Node _Current;

            public Enumerator(IEnumerator<T> enumerator, Node headElement)
            {
                _Enumerator = enumerator;
                _Current = headElement;
            }

            public T Current
            {
                get { return _Current.Value; }
            }

            public void Dispose()
            {
                _Enumerator.Dispose();
            }

            object IEnumerator.Current
            {
                get { return Current; }
            }

            public bool MoveNext()
            {
                if (_Current.Next != null)
                {
                    _Current = _Current.Next;
                    return true;
                }
                else if (_Enumerator.MoveNext())
                {
                    _Current.Next = new Node
                    {
                        Value = _Enumerator.Current
                    };
                    _Current = _Current.Next;
                    return true;
                }
                else
                {
                    _Enumerator.Dispose();
                    return false;
                }
            }

            public void Reset()
            {
                throw new NotImplementedException();
            }
        }

        private IEnumerator<T> _Enumerator;
        private Node _FirstElement;

        public EnumeratorEnumerable(IEnumerator<T> enumerator)
        {
            _Enumerator = enumerator;
            _FirstElement = new Node
            {
                Next = null,
                Value = enumerator.Current
            };
        }

        public IEnumerator<T> GetEnumerator()
        {
            return new Enumerator(_Enumerator, _FirstElement);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }

    public static class EnumeratorExtensions
    {
        public static IEnumerable<T> Remaining<T>(
            this IEnumerator<T> enumerator)
        {
            return new EnumeratorEnumerable<T>(enumerator);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            List<int> values = new List<int> { 1, 2, 3, 4, 5 };
            IEnumerator<int> enumerator = values.GetEnumerator();
            enumerator.MoveNext();
            enumerator.MoveNext();

            var enumerable = enumerator.Remaining();
            foreach (var i in enumerable)
                Console.Out.WriteLine(i);
            foreach (var i in enumerable)
                Console.Out.WriteLine(i);
        }
    }
}

Результат запуска этой программы:

3
4
5
3
4
5
2 голосов
/ 14 мая 2010

Take и Skip - это два метода, которые вы хотите использовать:

IEnumerable<int> sequence = ...
IEnumerable<int> pair = sequence.Take(2); //First two elements
IEnumerable<int> remaining = sequence.Skip(2);
0 голосов
/ 02 октября 2012

Если ваша цель - использовать foreach на IEnumerator<T> напрямую, я бы предложил что-то вроде этого:

public struct WrappedEnumerator<T>
{
    T myEnumerator;
    public T GetEnumerator() { return myEnumerator; }
    public WrappedEnumerator(T theEnumerator) { myEnumerator = theEnumerator; }
}
public static class AsForEachHelper
{
    static public WrappedEnumerator<IEnumerator<T>> AsForEach<T>(this IEnumerator<T> theEnumerator)
        { return new WrappedEnumerator<IEnumerator<T>>(theEnumerator);}

    static public WrappedEnumerator<System.Collections.IEnumerator> AsForEach(this System.Collections.IEnumerator theEnumerator) 
        { return new WrappedEnumerator<System.Collections.IEnumerator>(theEnumerator); }

    [Obsolete("Structs implementing IEnumerator<T> should be boxed before use", false)]
    static public WrappedEnumerator<System.Collections.IEnumerator> AsForEach<T>(this T theEnumerator) where T : struct, System.Collections.IEnumerator 
    { return new WrappedEnumerator<System.Collections.IEnumerator>(theEnumerator) ; }
}

Если foo является переменной типа IEnumerator, IEnumerator<T> или любого типа class , полученного из любого из них, можно просто сказать foreach (whatever in foo.AsForEach()); если цикл завершается рано, любые элементы, которые не были прочитаны, останутся в перечислителе. Однако обратите внимание, что оператор типа myEnumerator Foo=someList.GetEnumerator(), где someList - это List<T>, определит myEnumerator как тип struct , что несовместимо с методом WrappedEnumerator<T>. Если кто-то действительно смелый, можно удалить тег Obsolete (или изменить его параметр на false), чтобы разрешить использование AsForEach с распакованными перечислителями, но следует отметить, что вызов AsForEach для struct-type перечислитель может сделать снимок состояния перечисления, и перечисление этого снимка может не повлиять на состояние оригинала.

...