IEnumerable <t>表示IEnumerable <t>序列的“rest”</t> </t>

时间:2010-05-13 20:11:17

标签: .net linked-list ienumerable ienumerator

如果我正在浏览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 个答案:

答案 0 :(得分:3)

如果你必须使用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;
}

答案 1 :(得分:2)

TakeSkip是您要使用的两种方法:

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

答案 2 :(得分:2)

如果你想取一个IEnumerator<T>并得到一个IEnumerable<T>代表序列的其余部分,从字面上看,你将不得不做一些魔术来实现目标。

这样做的原因是,一般来说,枚举可以多次枚举,而枚举器则不能,它本身就是“多次”的一种。

首先,您可以尝试找出您正在处理的集合类型,从而在原始枚举器的其余部分之上返回一个合适的枚举器。你要去的原因。

或者......你可以将枚举器的其余部分缓存到一个新的集合中并返回它。这当然会消耗你的原始枚举器,无论可能是什么,并且在时间或内存方面可能很昂贵。

或者......你可以做几个人建议的,不要实际返回枚举器,而是使用可枚举类的Skip和Take方法返回你想要的东西。这将返回一个新的枚举,每次枚举时,它将枚举原始的可枚举,跳过前两个项,并生成其余项。

让我改写最后一段。如果您不尝试将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

答案 3 :(得分:0)

如果您的目标是能够直接在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) ; }
}

如果fooIEnumeratorIEnumerator<T>或任何类型的变量,则可以简单地说foreach (whatever in foo.AsForEach()) ;如果循环提前退出,则任何未读取的项目将保留在枚举器中。但请注意,myEnumerator Foo=someList.GetEnumerator() someList List<T>myEnumerator之类的语句会将WrappedEnumerator<T>定义为 struct 类型,与此类型不兼容Obsolete方法。如果一个人真的很勇敢,可以删除false标记(或将其参数更改为AsForEach)以允许AsForEach使用未装箱的枚举器,但应注意调用{{ struct-type枚举器上的1}}可能会获取枚举状态的快照,并枚举该快照可能不会影响原始状态。