Подтвердить что ты не робот

Есть ли встроенный способ преобразования IEnumerator в IEnumerable

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

4b9b3361

Ответ 1

Вы можете использовать следующее, которое будет работать 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

Самый простой способ конвертации, который я могу придумать, - через выражение 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);

Ответ 3

EnumeratorEnumerable<T>

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

Я использую параметры Enumerator, как в концепции С++ forward_iterator.

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

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

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

  • 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

Ответ 4

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

Ответ 5

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

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

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

Ответ 6

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;
  }
}

Ответ 7

Это вариант, который я написал... Специфика немного отличается. Я хотел сделать 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().

Ответ 8

Другие ответы здесь... странные. 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 required.
    fooEnumerator.Reset();

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

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