web-dev-qa-db-fra.com

Existe-t-il un moyen intégré de convertir IEnumerator en IEnumerable?

Existe-t-il un moyen intégré de convertir IEnumerator<T> en IEnumerable<T>?

40
Sam Saffron

Vous pouvez utiliser ce qui suit qui va kinda fonctionne. 

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 
}

Cela vous posera des problèmes lorsque les utilisateurs s'attendent à des appels successifs à GetEnumerator pour renvoyer des énumérateurs différents par rapport au même. Mais s'il s'agit d'une utilisation unique dans un scénario very / contraint, cela pourrait vous débloquer.

Je suggère cependant d'essayer de ne pas le faire, car je pense que cela finira par vous hanter. 

Jonathan propose une option plus sûre. Vous pouvez utiliser l'énumérateur et créer un List<T> des éléments restants. 

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

Le moyen le plus simple de convertir auquel je peux penser est via la déclaration de rendement

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

par rapport à la version de liste, cela présente l'avantage de ne pas énumérer la liste complète avant de renvoyer un IEnumerable. En utilisant la déclaration de rendement, vous ne parcourez que les éléments dont vous avez besoin, alors que vous utilisez la version de liste, vous parcourez d’abord tous les éléments de la liste, puis tous les éléments dont vous avez besoin.

pour un peu plus de plaisir, vous pouvez le changer en 

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

vous seriez alors capable d'utiliser linq sur votre énumérateur comme:

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

EnumeratorEnumerable<T>

Un adaptateur filetable, réinitialisable de IEnumerator<T> à IEnumerable<T>

J'utilise des paramètres d'énumérateur comme dans le concept forward_iterator de C++. 

Je conviens que cela peut prêter à confusion, car trop de gens vont supposer que les énumérateurs sont/like/Enumerables, mais ils ne le sont pas. 

Cependant, la confusion est alimentée par le fait que IEnumerator contient la méthode Reset. Voici mon idée de la mise en œuvre la plus correcte. Il utilise l'implémentation de IEnumerator.Reset ()

Une différence majeure entre Enumerable et et Enumerator est qu’un Enumerable peut créer plusieurs énumérateurs simultanément. Cette implémentation demande beaucoup de travail pour faire en sorte que cela ne se produise jamais pour le type EnumeratorEnumerable<T>. Il y a deux EnumeratorEnumerableModes:

  • Blocking (ce qui signifie qu'un deuxième appelant attendra simplement que le premier énumération soit terminé)
  • NonBlocking (ce qui signifie qu'une deuxième demande (simultanée) pour un énumérateur lève simplement une exception)

Note 1: 74 lignes implémentent, 79 lignes testent le code :)

Note 2: Je n'ai fait référence à aucun framework de tests unitaires pour SO convenance

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

Note 3: Je pense que l'implémentation du verrouillage de thread (particulièrement autour de BusyTable) est assez laide; Cependant, je ne voulais pas recourir à ReaderWriterLock(LockRecursionPolicy.NoRecursion) et ne voulais pas supposer .Net 4.0 pour SpinLock

9
sehe

Nope, IEnumerator <> et IEnumerable <> sont des bêtes totalement différentes.

3
Jason Watts

Comme l'a dit Jason Watts - non, pas directement.

Si vous le souhaitez vraiment, vous pouvez parcourir IEnumerator <T>, mettre les éléments dans une liste <T> et les renvoyer, mais j’imagine que ce n’est pas ce que vous cherchiez à faire.

La raison fondamentale pour laquelle vous ne pouvez pas aller dans cette direction (IEnumerator <T> vers un IEnumerable <T>) est que IEnumerable <T> représente un ensemble pouvant être énuméré, mais IEnumerator <T> est une énumération spécifique couvrant un ensemble d'éléments. - Vous ne pouvez pas transformer l'instance spécifique dans la chose qui l'a créée.

1
Jonathan Rupp

Ceci est une variante que j'ai écrite ... Le spécifique est un peu différent. Je voulais faire un MoveNext() sur un IEnumerable<T>, vérifier le résultat, puis tout recaler dans un nouveau IEnumerator<T> qui était "complet" (donc qui incluait même l'élément du IEnumerable<T> déjà extrait)

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

Utilisation:

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

Maintenant, le premier GetEnumerator() qui sera demandé à enumerable2 sera donné par l’énumérateur enumerator. À partir de la seconde, la enumerable.GetEnumerator() sera utilisée.

0
xanatos

Les autres réponses ici sont ... étranges. IEnumerable<T> a une seule méthode, GetEnumerator(). Et un IEnumerable<T> doit implémenter IEnumerable, qui a également une seule méthode, GetEnumerator() (la différence étant que l'une est générique sur T et l'autre ne l'est pas). Donc, il devrait être clair comment transformer un IEnumerator<T> en un 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);

Cependant, rien de tout cela ne devrait être nécessaire car il est inhabituel d'avoir un IEnumerator<T> qui ne soit pas accompagné d'un IEnumerable<T> qui en renvoie une instance à partir de sa méthode GetEnumerator. Si vous écrivez votre propre IEnumerator<T>, vous devez certainement fournir le IEnumerable<T>. Et en réalité, c'est l'inverse ... Un IEnumerator<T> est censé être une classe privée qui itère sur les instances d'une classe publique qui implémente IEnumerable<T>.

0
Jim Balter
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;
  }
}
0
Carlo V. Dango