Есть ли встроенный способ конвертировать IEnumerator в IEnumerable - PullRequest
43 голосов
/ 23 июня 2009

Есть ли встроенный способ конвертировать IEnumerator<T> в IEnumerable<T>?

Ответы [ 8 ]

60 голосов
/ 23 июня 2009

Самый простой способ конвертации, который я могу придумать, - это выражение дохода

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);
22 голосов
/ 23 июня 2009

Вы можете использовать следующее, которое будет своего рода работать.

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;
}
9 голосов
/ 12 апреля 2011

EnumeratorEnumerable<T>

Потокобезопасный, сбрасываемый адаптер от IEnumerator<T> до IEnumerable<T>

Я использую параметры перечислителя, как в C ++ forward_iterator.

Я согласен, что это может привести к путанице, поскольку слишком многие люди действительно будут считать, что перечислители / как / перечислимы, но это не так.

Однако путаница вызвана тем фактом, что IEnumerator содержит метод Reset. Вот моя идея наиболее правильной реализации. Он использует реализацию IEnumerator.Reset ()

Основное различие между Enumerable и Enumerator заключается в том, что Enumerable может создавать несколько перечислителей одновременно. Эта реализация проделывает большую работу, чтобы убедиться, что этого никогда не произойдет для типа EnumeratorEnumerable<T>. Есть два EnumeratorEnumerableMode с:

  • Blocking (это означает, что второй абонент просто будет ждать, пока не завершится первое перечисление)
  • NonBlocking (что означает, что второй (параллельный) запрос для перечислителя просто вызывает исключение)

Примечание 1: 74 строки - реализация, 79 строк - код тестирования:)

Примечание 2: Я не обращался к какой-либо структуре модульного тестирования для удобства SO

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) и не хотел принимать .Net 4.0 для SpinLock

3 голосов
/ 23 июня 2009

Нет, IEnumerator <> и IEnumerable <> полностью разные звери.

1 голос
/ 23 июня 2009

Как сказал Джейсон Уоттс - нет, не напрямую.

Если вы действительно хотите, вы можете перебрать IEnumerator , поместить элементы в список и вернуть его, но я предполагаю, что это не то, что вы хотите сделать.

Основная причина, по которой вы не можете пойти в этом направлении (IEnumerator к IEnumerable ), заключается в том, что IEnumerable представляет набор, который можно перечислить, но IEnumerator - это конкретная перечисление над набор предметов - вы не можете превратить конкретный экземпляр обратно в то, что его создало.

0 голосов
/ 29 ноября 2018

Другие ответы здесь ... странные. 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>, который не поставляется с IEnumerable<T>, который возвращает его экземпляр из метода GetEnumerator. Если вы пишете свой собственный IEnumerator<T>, вы, безусловно, должны предоставить IEnumerable<T>. И действительно, все наоборот ... IEnumerator<T> предназначен для использования в качестве закрытого класса, который перебирает экземпляры открытого класса, реализующего IEnumerable<T>.

0 голосов
/ 20 марта 2015

Это вариант, который я написал ... Специфика немного другая. Я хотел сделать MoveNext() для IEnumerable<T>, проверить результат, а затем свернуть все в новый IEnumerator<T>, который был «завершен» (чтобы в него был включен даже элемент IEnumerable<T>, который я уже извлек)

// 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().

0 голосов
/ 06 февраля 2013
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;
  }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...