是否有内置的方法将IEnumerator转换为IEnumerable

时间:2009-06-22 21:57:38

标签: c# .net linq

是否有内置方式将IEnumerator<T>转换为IEnumerable<T>

9 个答案:

答案 0 :(得分:59)

我能想到的最简单的转换方式是通过yield语句

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

与列表版本相比,它具有在返回IEnumerable之前不枚举整个列表的优点。使用yield语句,你只需迭代你需要的项目,而使用列表版本,你首先遍历列表中的所有项目,然后迭代你需要的所有项目。

为了更有趣,您可以将其更改为

public static IEnumerable<K> Select<K,T>(this IEnumerator<T> e, 
                                         Func<K,T> selector) {
      while ( e.MoveNext() ) {
        yield return selector(e.Current);
      }
    }

然后你就可以在你的枚举器上使用linq,如:

IEnumerator<T> enumerator;
var someList = from item in enumerator
               select new classThatTakesTInConstructor(item);

答案 1 :(得分:21)

您可以使用以下 kinda 工作。

public class FakeEnumerable<T> : IEnumerable<T> {
  private IEnumerator<T> m_enumerator;
  public FakeEnumerable(IEnumerator<T> e) {
    m_enumerator = e;
  }
  public IEnumerator<T> GetEnumerator() { 
    return m_enumerator;
  }
  // Rest omitted 
}

当人们期望连续调用GetEnumerator返回不同的枚举器而不是同一个枚举器时,这会让你遇到麻烦。但是,如果它仅在非常约束的场景中使用一次,则可以解锁您。

我建议尽管你尝试不这样做,因为我认为它最终会回来困扰你。

一个更安全的选择是乔纳森建议的。您可以花费枚举器并创建其余项目的List<T>

public static List<T> SaveRest<T>(this IEnumerator<T> e) {
  var list = new List<T>();
  while ( e.MoveNext() ) {
    list.Add(e.Current);
  }
  return list;
}

答案 2 :(得分:9)

EnumeratorEnumerable<T>

IEnumerator<T>IEnumerable<T>

的线程安全,可重置适配器

我在C ++ forward_iterator概念中使用Enumerator参数。

我同意这可能导致混淆,因为太多人确实会认为调查员是/喜欢/可枚举,但他们不是。

但是,IEnumerator包含Reset方法会导致混淆。这是我对最正确实现的想法。它利用了IEnumerator.Reset()

的实现

Enumerable和Enumerator之间的主要区别在于,Enumerable可能能够同时创建多个枚举器。这个实现付出了大量的工作,以确保EnumeratorEnumerable<T>类型永远不会发生这种情况。有两个EnumeratorEnumerableMode s:

  • Blocking(意味着第二个来电者只会等到第一次枚举完成)
  • NonBlocking(意味着对枚举器的第二个(并发)请求只会引发异常)

注1:实行74行,79行测试代码:)

注意2:我没有提及任何单元测试框架以方便使用

using System;
using System.Diagnostics;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using System.Threading;

namespace EnumeratorTests
{
    public enum EnumeratorEnumerableMode
    {
        NonBlocking,
        Blocking,
    }

    public sealed class EnumeratorEnumerable<T> : IEnumerable<T>
    {
        #region LockingEnumWrapper

        public sealed class LockingEnumWrapper : IEnumerator<T>
        {
            private static readonly HashSet<IEnumerator<T>> BusyTable = new HashSet<IEnumerator<T>>();
            private readonly IEnumerator<T> _wrap;

            internal LockingEnumWrapper(IEnumerator<T> wrap, EnumeratorEnumerableMode allowBlocking) 
            {
                _wrap = wrap;

                if (allowBlocking == EnumeratorEnumerableMode.Blocking)
                    Monitor.Enter(_wrap);
                else if (!Monitor.TryEnter(_wrap))
                    throw new InvalidOperationException("Thread conflict accessing busy Enumerator") {Source = "LockingEnumWrapper"};

                lock (BusyTable)
                {
                    if (BusyTable.Contains(_wrap))
                        throw new LockRecursionException("Self lock (deadlock) conflict accessing busy Enumerator") { Source = "LockingEnumWrapper" };
                    BusyTable.Add(_wrap);
                }

                // always implicit Reset
                _wrap.Reset();
            }

            #region Implementation of IDisposable and IEnumerator

            public void Dispose()
            {
                lock (BusyTable)
                    BusyTable.Remove(_wrap);

                Monitor.Exit(_wrap);
            }
            public bool MoveNext()      { return _wrap.MoveNext(); }
            public void Reset()         { _wrap.Reset(); }
            public T Current            { get { return _wrap.Current; } }
            object IEnumerator.Current  { get { return Current; } }

            #endregion
        }

        #endregion

        private readonly IEnumerator<T> _enumerator;
        private readonly EnumeratorEnumerableMode _allowBlocking;

        public EnumeratorEnumerable(IEnumerator<T> e, EnumeratorEnumerableMode allowBlocking)
        {
            _enumerator = e;
            _allowBlocking = allowBlocking;
        }

        private LockRecursionPolicy a;
        public IEnumerator<T> GetEnumerator()
        {
            return new LockingEnumWrapper(_enumerator, _allowBlocking);
        }

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

    class TestClass
    {
        private static readonly string World = "hello world\n";

        public static void Main(string[] args)
        {
            var master = World.GetEnumerator();
            var nonblocking = new EnumeratorEnumerable<char>(master, EnumeratorEnumerableMode.NonBlocking);
            var blocking    = new EnumeratorEnumerable<char>(master, EnumeratorEnumerableMode.Blocking);

            foreach (var c in nonblocking)  Console.Write(c); // OK (implicit Reset())
            foreach (var c in blocking)     Console.Write(c); // OK (implicit Reset())
            foreach (var c in nonblocking)  Console.Write(c); // OK (implicit Reset())
            foreach (var c in blocking)     Console.Write(c); // OK (implicit Reset())

            try
            {
                var willRaiseException = from c1 in nonblocking from c2 in nonblocking select new {c1, c2};
                Console.WriteLine("Cartesian product: {0}", willRaiseException.Count()); // RAISE
            }
            catch (Exception e) { Console.WriteLine(e); }

            foreach (var c in nonblocking)  Console.Write(c); // OK (implicit Reset())
            foreach (var c in blocking)     Console.Write(c); // OK (implicit Reset())

            try
            {
                var willSelfLock = from c1 in blocking from c2 in blocking select new { c1, c2 };
                Console.WriteLine("Cartesian product: {0}", willSelfLock.Count()); // LOCK
            }
            catch (Exception e) { Console.WriteLine(e); }

            // should not externally throw (exceptions on other threads reported to console)
            if (ThreadConflictCombinations(blocking, nonblocking))
                throw new InvalidOperationException("Should have thrown an exception on background thread");
            if (ThreadConflictCombinations(nonblocking, nonblocking))
                throw new InvalidOperationException("Should have thrown an exception on background thread");

            if (ThreadConflictCombinations(nonblocking, blocking))
                Console.WriteLine("Background thread timed out");
            if (ThreadConflictCombinations(blocking, blocking))
                Console.WriteLine("Background thread timed out");

            Debug.Assert(true); // Must be reached
        }

        private static bool ThreadConflictCombinations(IEnumerable<char> main, IEnumerable<char> other)
        {
            try
            {
                using (main.GetEnumerator())
                {
                    var bg = new Thread(o =>
                        {
                            try { other.GetEnumerator(); }
                            catch (Exception e) { Report(e); }
                        }) { Name = "background" };
                    bg.Start();

                    bool timedOut = !bg.Join(1000); // observe the thread waiting a full second for a lock (or throw the exception for nonblocking)

                    if (timedOut)
                        bg.Abort();

                    return timedOut;
                }
            } catch
            {
                throw new InvalidProgramException("Cannot be reached");
            }
        }

        static private readonly object ConsoleSynch = new Object();
        private static void Report(Exception e)
        {
            lock (ConsoleSynch)
                Console.WriteLine("Thread:{0}\tException:{1}", Thread.CurrentThread.Name, e);
        }
    }
}

注3:我认为线程锁定的实现(特别是在BusyTable附近)非常难看;但是,我不想诉诸ReaderWriterLock(LockRecursionPolicy.NoRecursion)并且不想假设。{4.0} for SpinLock

答案 3 :(得分:3)

不,IEnumerator&lt;&gt;和IEnumerable&lt;&gt;完全是不同的野兽。

答案 4 :(得分:1)

正如Jason Watts所说 - 不,不是直接。

如果你真的想要,你可以遍历IEnumerator&lt; T&gt;,将项目放入List&lt; T&gt;,然后返回,但我猜这不是你想要做的。

你无法走向那个方向的基本原因(IEnumerator&lt; T&gt;到IEnumerable&lt; T&gt;)是IEnumerable&lt; T&gt;表示可以枚举的集合,但IEnumerator&lt; T&gt;表示可以枚举的集合。是对一组项目的特定枚举 - 您无法将特定实例转换回创建它的事物。

答案 5 :(得分:0)

static class Helper
{
  public static List<T> SaveRest<T>(this IEnumerator<T> enumerator)
  {
    var list = new List<T>();
    while (enumerator.MoveNext())
    {
      list.Add(enumerator.Current);
    }
    return list;
  }
  public static ArrayList SaveRest(this IEnumerator enumerator)
  {
    var list = new ArrayList();
    while (enumerator.MoveNext())
    {
      list.Add(enumerator.Current);
    }
    return list;
  }
}

答案 6 :(得分:0)

这是我写的一个变种......具体有点不同。我想在MoveNext()上执行IEnumerable<T>,检查结果,然后在一个“完整”的新IEnumerator<T>中滚动所有内容(因此甚至包含IEnumerable<T>的元素1}}我已经提取过了)

// Simple IEnumerable<T> that "uses" an IEnumerator<T> that has
// already received a MoveNext(). "eats" the first MoveNext() 
// received, then continues normally. For shortness, both IEnumerable<T>
// and IEnumerator<T> are implemented by the same class. Note that if a
// second call to GetEnumerator() is done, the "real" IEnumerator<T> will
// be returned, not this proxy implementation.
public class EnumerableFromStartedEnumerator<T> : IEnumerable<T>, IEnumerator<T>
{
    public readonly IEnumerator<T> Enumerator;

    public readonly IEnumerable<T> Enumerable;

    // Received by creator. Return value of MoveNext() done by caller
    protected bool FirstMoveNextSuccessful { get; set; }

    // The Enumerator can be "used" only once, then a new enumerator
    // can be requested by Enumerable.GetEnumerator() 
    // (default = false)
    protected bool Used { get; set; }

    // The first MoveNext() has been already done (default = false)
    protected bool DoneMoveNext { get; set; }

    public EnumerableFromStartedEnumerator(IEnumerator<T> enumerator, bool firstMoveNextSuccessful, IEnumerable<T> enumerable)
    {
        Enumerator = enumerator;
        FirstMoveNextSuccessful = firstMoveNextSuccessful;
        Enumerable = enumerable;
    }

    public IEnumerator<T> GetEnumerator()
    {
        if (Used)
        {
            return Enumerable.GetEnumerator();
        }

        Used = true;
        return this;
    }

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

    public T Current
    {
        get
        {
            // There are various school of though on what should
            // happens if called before the first MoveNext() or
            // after a MoveNext() returns false. We follow the 
            // "return default(TInner)" school of thought for the
            // before first MoveNext() and the "whatever the 
            // Enumerator wants" for the after a MoveNext() returns
            // false
            if (!DoneMoveNext)
            {
                return default(T);
            }

            return Enumerator.Current;
        }
    }

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

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

    public bool MoveNext()
    {
        if (!DoneMoveNext)
        {
            DoneMoveNext = true;
            return FirstMoveNextSuccessful;
        }

        return Enumerator.MoveNext();
    }

    public void Reset()
    {
        // This will 99% throw :-) Not our problem.
        Enumerator.Reset();

        // So it is improbable we will arrive here
        DoneMoveNext = true;
    }
}

使用:

var enumerable = someCollection<T>;

var enumerator = enumerable.GetEnumerator();
bool res = enumerator.MoveNext();
// do whatever you want with res/enumerator.Current

var enumerable2 = new EnumerableFromStartedEnumerator<T>(enumerator, res, enumerable);

现在,将通过枚举器GetEnumerator()enumerable2请求的第一个enumerator。从第二个开始,将使用enumerable.GetEnumerator()

答案 7 :(得分:0)

这里的其他答案很奇怪。 IEnumerable<T>只有一种方法,GetEnumerator()IEnumerable<T>必须实现IEnumerable,它也只有一种方法GetEnumerator()(不同之处在于,一种方法在T上是通用的,而另一种则不是)。因此,应该清楚如何将IEnumerator<T>变成IEnumerable<T>

    // using modern expression-body syntax
    public class IEnumeratorToIEnumerable<T> : IEnumerable<T>
    {
        private readonly IEnumerator<T> Enumerator;

        public IEnumeratorToIEnumerable(IEnumerator<T> enumerator) =>
            Enumerator = enumerator;

        public IEnumerator<T> GetEnumerator() => Enumerator;
        IEnumerator IEnumerable.GetEnumerator() => Enumerator;
    }

    foreach (var foo in new IEnumeratorToIEnumerable<Foo>(fooEnumerator))
        DoSomethingWith(foo);

    // and you can also do:

    var fooEnumerable = new IEnumeratorToIEnumerable<Foo>(fooEnumerator);

    foreach (var foo in fooEnumerable)
        DoSomethingWith(foo);

    // Some IEnumerators automatically repeat after MoveNext() returns false,
    // in which case this is a no-op, but generally it's required.
    fooEnumerator.Reset();

    foreach (var foo in fooEnumerable)
        DoSomethingElseWith(foo);

但是,不需要任何操作,因为不带IEnumerator<T>的{​​{1}}并不通过其IEnumerable<T>方法返回其实例的情况。如果您要编写自己的GetEnumerator,则应该提供IEnumerator<T>。确实是另一回事... IEnumerable<T>旨在成为私有类,该私有类在实现IEnumerator<T>的公共类的实例上进行迭代。

答案 8 :(得分:0)

使用 Factory 并修复 JaredPar's answer 中缓存的 IEnumerator 问题的解决方案允许更改枚举方式。

考虑一个简单的例子:我们想要自定义的 List<T> 包装器,允许与默认枚举一起以相反的顺序进行枚举。 List<T> 已经为默认枚举实现了 IEnumerator,我们只需要创建以相反顺序枚举的 IEnumerator。 (我们不会使用 List<T>.AsEnumerable().Reverse(),因为它会枚举列表两次)

public enum EnumerationType {
    Default = 0,
    Reverse
}

public class CustomList<T> : IEnumerable<T> {
    private readonly List<T> list;

    public CustomList(IEnumerable<T> list) => this.list = new List<T>(list);
    
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    
    //Default IEnumerable method will return default enumerator factory
    public IEnumerator<T> GetEnumerator() 
        => GetEnumerable(EnumerationType.Default).GetEnumerator();

    public IEnumerable<T> GetEnumerable(EnumerationType enumerationType)
        => enumerationType switch {
            EnumerationType.Default => new DefaultEnumeratorFactory(list),
            EnumerationType.Reverse => new ReverseEnumeratorFactory(list)
        };
    
    //Simple implementation of reverse list enumerator
    private class ReverseEnumerator : IEnumerator<T> {
        private readonly List<T> list;
        private int index;

        internal ReverseEnumerator(List<T> list) {
            this.list = list;
            index = list.Count-1;
            Current = default;
        }

        public void Dispose() { }

        public bool MoveNext() {
            if(index >= 0) {
                Current = list[index];
                index--;
                return true;
            }
            Current = default;
            return false;
        }

        public T Current { get; private set; }

        object IEnumerator.Current => Current;

        void IEnumerator.Reset() {
            index = list.Count - 1;
            Current = default;
        }
    }
    
    private abstract class EnumeratorFactory : IEnumerable<T> {
        protected readonly List<T> List;
        protected EnumeratorFactory(List<T> list) => List = list;
        
        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
        
        public abstract IEnumerator<T> GetEnumerator();
    }


    private class DefaultEnumeratorFactory : EnumeratorFactory {
        
        public DefaultEnumeratorFactory(List<T> list) : base(list) { }
        
        //Default enumerator is already implemented in List<T>
        public override IEnumerator<T> GetEnumerator() => List.GetEnumerator();
    }


    private class ReverseEnumeratorFactory : EnumeratorFactory {

        public ReverseEnumeratorFactory(List<T> list) : base(list) { }

        public override IEnumerator<T> GetEnumerator() => new ReverseEnumerator(List);
    }
    
}