一个表示 IEnumerable<T> 序列中“剩余”部分的 IEnumerable<T>。

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>IEnumerator<T> 上实现这一点,所以可能与潜在的无限、不确定的元素序列的概念不兼容。


很遗憾IEnumerable<T>上没有Clone方法。这可能会帮助你。您可以实现一个EnumerableEx<T>类,它包装了一个IEnumerable<T>并支持克隆。 - John Källén
rest = sequence.Skip(2)会复制你的例子,但不具有普适性... - jball
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;
}

2

TakeSkip是你想要使用的两种方法:

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

我知道这些方法,但它们要求你知道你在哪个数字元素上。假设我已经走了一部分序列并想要剩下的所有内容,我不想有一个计数器来跟踪我已经走了多远。(请参见@jball对问题的评论)。 - Hank

2
如果你想要获取一个IEnumerator并得到代表序列其余部分的IEnumerable,那么字面上来说,你需要进行一些魔法才能实现。
这是因为通常情况下,可枚举对象可以被多次枚举,而枚举器只能枚举一次,它只是其中的一个“多次”本身。
首先,你可以尝试找出你正在处理的集合类型,并在原始枚举器之上返回一个适当的枚举器。你所需要的原因就是这个。
或者...你可以将枚举器的剩余部分缓存到一个新的集合中并返回它。当然,这将消耗你原来的枚举器,无论它是什么,可能会在时间或内存方面很昂贵。
或者...你可以做一些其他人建议的事情,不实际返回枚举器,而是使用可枚举类的Skip和Take方法返回你想要的内容。这将返回一个新的可枚举对象,每次枚举时它都将枚举原始可枚举对象,跳过前两个项并生成其余项。
让我重新措辞一下最后一段。如果你不试图将IEnumerator的剩余部分作为新的可枚举对象返回,而是直接处理原始集合,那么处理起来就容易得多。
下面是一些缓存元素的代码。它的好处是,如果你从生成的可枚举对象中产生了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

0
如果您的目标是直接在 IEnumerator<T> 上使用 foreach,我建议尝试以下代码:
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()这样的语句,其中someList是一个List<T>,将会把myEnumerator定义为一个不兼容于WrappedEnumerator<T>方法的结构体类型。如果你真的很勇敢,可以移除Obsolete标记(或将其参数更改为false),以允许在未装箱的枚举器上使用AsForEach,但需要注意的是,在结构体类型的枚举器上调用AsForEach可能会对枚举状态进行快照,并且枚举该快照可能不会影响原始状态。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接