3

如果我正在走过一个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>or上进行此操作IEnumerator<T>,因此它可能与潜在的无限的、不确定的元素序列的概念不兼容。

4

4 回答 4

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;
}
于 2010-05-13T20:23:26.753 回答
2

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

IEnumerable<int> sequence = ...
IEnumerable<int> pair = sequence.Take(2); //First two elements
IEnumerable<int> remaining = sequence.Skip(2);
于 2010-05-13T20:20:56.450 回答
2

如果你想得到一个IEnumerator<T>并得到一个IEnumerable<T>代表序列的其余部分,从字面上看,你将不得不做一些魔法才能到达那里。

这样做的原因是,在一般意义上,一个可枚举可以被枚举多次,而一个枚举器不能,它本身只是那些“多次”中的一个。

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

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

或者......你可以按照几个人的建议去做,实际上不要返回枚举数,而是使用可枚举类的 Skip 和 Take 方法来返回你想要的。这将返回一个新的 enumerable,每次枚举时,它将枚举原始 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.

当然,如果你保留 enumerable 并枚举其中的所有元素,它将生成原始 enumerable 中所有元素的内存副本,正如我所说,这可能会很昂贵。

无论如何,这是代码。它不是线程安全的:

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
于 2010-05-13T20:27:57.277 回答
0

如果您的目标是能够直接使用foreachIEnumerator<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是类型 , 或派生自其中任何一个的任何类类型的变量IEnumeratorIEnumerator<T>则可以简单地foreach (whatever in foo.AsForEach()); 如果循环提前退出,任何未读取的项目都将保留在枚举器中。但是请注意,像myEnumerator Foo=someList.GetEnumerator()where someListis a这样的语句List<T>将定义myEnumerator结构类型,这与方法不兼容WrappedEnumerator<T>。如果真的很勇敢,可以删除Obsolete标签(或将其参数更改为false)以允许AsForEach与未装箱的枚举器一起使用,但应注意调用AsForEach结构类型的枚举器可能会获取枚举状态的快照,并枚举该快照可能不会影响原始状态。

于 2012-10-01T20:51:24.313 回答