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

Sam*_*ron 45 .net c# linq

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

Run*_* FS 63

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

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

与列表版本相比,这具有在返回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);
      }
    }
Run Code Online (Sandbox Code Playgroud)

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

IEnumerator<T> enumerator;
var someList = from item in enumerator
               select new classThatTakesTInConstructor(item);
Run Code Online (Sandbox Code Playgroud)

  • 那么你在陈述问题中没有说明(至少是明确的)要求.如果不需要多次迭代,缓存将会受到性能影响,Sam表示性能非常受关注,因此不包含缓存 (10认同)
  • -1:这不符合IEnumerable应该能够多次迭代的事实; 这里只能执行一次,因为源IEnumerator已经用完了.您需要第二次从IEnumerator缓存项目. (2认同)
  • +1无需定义类。 (2认同)
  • *您需要第二次缓存 IEnumerator 中的项目。* - 不。如果调用者想要重置枚举器,则由调用者决定: `foreach(var x in fooEnumerator.ToIEnumerable&lt;Foo&gt;()) { ... }; fooEnumerator.Reset(); foreach(var x in fooEnumerator.ToIEnumerable&lt;Foo&gt;()) { ... };` (2认同)

Jar*_*Par 22

你可以使用,这将在下面还挺工作.

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 
}
Run Code Online (Sandbox Code Playgroud)

当人们期望连续调用GetEnumerator返回不同的枚举器而不是同一个枚举器时,这会让你遇到麻烦.但如果它只是在一个非常有限的情况下使用一次,这可能会阻止你.

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

Jonathan建议采用更安全的选择.您可以花费枚举器并创建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;
}
Run Code Online (Sandbox Code Playgroud)


seh*_*ehe 9

EnumeratorEnumerable<T>

是线程安全,从复位适配器IEnumerator<T>IEnumerable<T>

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

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

然而,IEnumerator包含Reset方法的事实造成了混乱.这是我对最正确实现的想法.它利用IEnumerator.Reset()的实现

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

  • 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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

注3:我认为线程锁定的实现(特别是左右BusyTable)非常难看; 但是,我不想诉诸ReaderWriterLock(LockRecursionPolicy.NoRecursion),也不想假设.Net 4.0SpinLock