Pregunta

Si estoy caminando a través de un IEnumerable<T>, ¿hay alguna manera de conseguir un nuevo IEnumerable<T> que representa los elementos restantes después de la actual.

Por ejemplo, me gustaría escribir un método de extensión 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
}

Estoy pensando en la colección de una especie de lista simplemente enlazada, por lo que no debe haber una manera de representar los elementos restantes, ¿verdad? No veo ninguna manera de hacer esto expuesta en cualquiera IEnumerable<T> o IEnumerator<T>, así que quizás es incompatible con la noción de una secuencia potencialmente ilimitada, no determinista de elementos.

¿Fue útil?

Solución

Si tiene que usar IEnumerator<T> y no IEnumerable<T> (donde todos los buenos métodos de extensión son) aquí está dos métodos sencillos.

Éste sólo puede ser enumeró una sola vez (y se une a la enumerable original, lo que significa que puede terminar con excepciones si otro hilo cambia la lista de fuentes):

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

Y éste construye una lista y se puede enumerar varias veces (y está desconectado del empadronador original, por lo que no tiene que preocuparse de su fuente IEnumerable cambiar):

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;
}

Otros consejos

Tome y Saltar son los dos métodos que desea utilizar:

IEnumerable<int> sequence = ...
IEnumerable<int> pair = sequence.Take(2); //First two elements
IEnumerable<int> remaining = sequence.Skip(2);

Si usted quiere tomar una IEnumerator<T> y obtener una IEnumerable<T> representa el resto de la secuencia, tomada literalmente, va a tener que hacer un poco de magia para llegar allí.

La razón de esto es que, en sentido general, una numerable se puede enumerar varias veces, mientras que el encuestador no puede, es sólo una de esas "varias veces" por sí mismo.

En primer lugar, se puede tratar de averiguar qué tipo de colección que está tratando, y así devolver un empadronador apropiada en la parte superior del resto del empadronador originales. La razón por la que vas.

O ... sólo puede almacenar en caché el resto del empadronador en una nueva colección y volver eso. Esto, por supuesto consumir su empadronador original, sea lo que sea, y podría ser costoso, en términos de tiempo o la memoria.

O ... puedes hacer lo que algunos han sugerido, en realidad no devolver el empadronador, pero en lugar de utilizar las funciones de salto y Take métodos de la clase enumerable para devolver lo que quiere. Esto devolverá un nuevo enumerables, que cada vez se enumera, se enumerará el enumerables original, saltar los dos primeros puntos, y producir el resto.

Déjame reformular que último párrafo. Si no intenta devolver el resto de la IEnumerator<T> como un nuevo enumerable, pero en cambio sólo se ocupa de la colección original, se hace mucho más fácil de tratar.

Aquí hay un código que almacena en caché los elementos. Tiene la ventaja de que si se producen 2 o más enumeradores (o incluso sólo 1) de la enumerables resultante, y luego dejar que la marcha enumerables fuera de alcance, ya que los enumeradores comienzan a moverse a través de los elementos, permitirá que el recolector de basura para empezar la recogida de los elementos que han sido aprobadas por.

En otras palabras, si usted hace esto:

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.

Por supuesto, si se mantiene el torno numerable, y enumerar sobre todos los elementos que lo integran, se producirá una copia en memoria de todos los elementos de la enumerables original, que, como ya he dicho, se puede ser costoso.

De todos modos, aquí está el código. No es seguro para subprocesos:

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);
        }
    }
}

La salida de la ejecución de este programa es:

3
4
5
3
4
5

Si su objetivo es ser capaz de utilizar foreach en una IEnumerator<T> directamente, me gustaría sugerir algo como esto:

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) ; }
}

Si foo es una variable de tipo IEnumerator, IEnumerator<T>, o cualquier clase tipo derivado de cualquiera de los dos, uno puede decir simplemente foreach (whatever in foo.AsForEach()); si el bucle sale temprano, los elementos que no se han leído permanecerán en el empadronador. Tenga en cuenta, sin embargo, que una declaración como myEnumerator Foo=someList.GetEnumerator() donde someList es un List<T> definirá myEnumerator como un tipo de estructura, que es incompatible con el método WrappedEnumerator<T>. Si uno es realmente valiente, se puede quitar la etiqueta Obsolete (o cambiar su parámetro a false) para permitir el uso de AsForEach con encuestadores sin caja, pero hay que señalar que llamar AsForEach en un empadronador del tipo de estructura podría tomar una instantánea de la enumeración estado, y enumerando esa instantánea podría no afectar el estado del original.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top