Feedback

C# - RingEnumerator - Liste immer im Kreis durchlaufen

Veröffentlicht von am 1/28/2016
(0 Bewertungen)
Die hier gezeigte Klasse nimmt einen Enumerator (IEnumerator<T>) oder eine Auflistung (IEnumerable<T>) entgegen und durchläuft diese immer wieder im Kreis. Würde mein beispielsweise ein Array mit den Werten 1,2,3 übergeben, würde diese Klasse 1,2,3,1,2,3,1,2,3,1,2,3,... usw. zurück geben.

Mit Hilfe der break-Anweisung in einer foreach-Schleife bzw. passenden LINQ-Methoden oder einem manuellen verwenden der IEnumerator Schnittstelle kann man die "Dauerschleife" wieder anhalten.

Diese Klasse auf GitHub
http://bit.ly/1KdwukE

UnitTests zu diese Klasse auf GitHub
http://bit.ly/1WQ99rd
using System;
using System.Collections;
using System.Collections.Generic;

namespace Koopakiller.Linq
{
    public static class Extensions
    {
        /// <summary>
        /// Duchläuft eine Auflistung immer wieder im Kreis.
        /// </summary>
        /// <typeparam name="T">Der Typ der Elemente in der Auflistung.</typeparam>
        /// <param name="source">Die zu durchlaufende Auflistung.</param>
        /// <param name="useCache">Wenn <c>true</c> dann wird <paramref name="source"/> nur einmal 
        /// durchlaufen; andernfalls wird mehrfach über die Auflistung iteriert.</param>
        public static IEnumerable<T> Repeat<T>(this IEnumerable<T> source, bool useCache = true)
        {
            if (useCache)
            {
                source = source.ToList();
            }
            while (true)
            {
                // ReSharper disable once PossibleMultipleEnumeration
                foreach (var item in source)
                {
                    yield return item;
                }
            }
            // ReSharper disable once FunctionNeverReturns
        }
    }

    public sealed class RingEnumerator<T> : IEnumerable<T>, IEnumerator<T>
    {
        #region Fields

        private readonly IEnumerator<T> _enumerator;

        private bool _lastMoveNextWasFalse = true;
        private bool _iterationStarted;
        private bool _useCache;

        private List<T> _cachedList;
        private int _currentCachedItemIndex = -1;
        private bool _cacheBuildComplete;

        #endregion

        #region .ctor

        public RingEnumerator(IEnumerable<T> source) : this(source?.GetEnumerator()) { }

        public RingEnumerator(IEnumerator<T> enumerator)
        {
            if (enumerator == null)
            {
                throw new ArgumentNullException(nameof(enumerator), $"{nameof(enumerator)} cannot be null.");
            }
            this._enumerator = enumerator;
        }

        #endregion

        #region Properties

        public bool UseCache
        {
            get { return this._useCache; }
            set
            {
                if (this._iterationStarted)
                {
                    throw new InvalidOperationException("The enumeration has already started.");
                }
                this._useCache = value;
            }
        }

        #endregion

        #region IEnumerator

        object IEnumerator.Current => this.Current;

        public T Current
        {
            get
            {
                if (this.UseCache && this._cacheBuildComplete)
                {
                    return this._cachedList[this._currentCachedItemIndex];
                }
                else
                {
                    return this._enumerator.Current;
                }
            }
        }

        public void Reset()
        {
            this._currentCachedItemIndex = -1;
            this._cacheBuildComplete = false;
            this._iterationStarted = false;
            this._enumerator.Reset();
        }

        public bool MoveNext()
        {
            if (!this._iterationStarted && this.UseCache)
            {
                this._cacheBuildComplete = false;
                this._cachedList = new List<T>();
            }

            if (this.UseCache && this._cacheBuildComplete)
            {
                ++this._currentCachedItemIndex;
                if (this._currentCachedItemIndex >= this._cachedList.Count)
                {
                    this._currentCachedItemIndex = 0;
                }
                return true;
            }
            else
            {
                this._iterationStarted = true;
                if (this._enumerator.MoveNext())
                {
                    this._lastMoveNextWasFalse = false;
                    if (this.UseCache)
                    {
                        this._cachedList.Add(this.Current);
                    }
                    return true;
                }

                if (this.UseCache)
                {
                    this._cacheBuildComplete = true;
                    this._currentCachedItemIndex = 0;
                    return this._cachedList.Count > 0;
                }
                else
                {
                    if (this._lastMoveNextWasFalse)
                    {
                        return false;
                    }
                    this._lastMoveNextWasFalse = true;
                    this._enumerator.Reset();
                    this._enumerator.MoveNext();
                    return true;
                }
            }
        }

        #endregion

        #region IDisposable

        public void Dispose()
        {
            this._enumerator.Dispose();
        }

        #endregion

        #region IEnumerable

        public IEnumerator<T> GetEnumerator() => this;

        IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();

        #endregion
    }
}

Abgelegt unter LINQ, Ring, Enumerator, Iterator, Schleife.

4 Kommentare zum Snippet

Anonymous2 schrieb am 1/30/2016:
Schön, aber etwas overkill oder? Was hälst du davon?

public static class IEnumerableExtension
{
public static IEnumerable<T> Repeat<T>(this IEnumerable<T> e)
{
var buffer = new List<T>();

foreach (var elem in e)
{
buffer.Add(elem);

yield return elem;
}

while (true)
{
foreach (var elem in buffer)
{
yield return elem;
}
}
}
}
Koopakiller schrieb am 1/30/2016:
Klar, beim alltäglichen Programmieren hätte ich mir auch nicht die Mühe gemacht so eine Klasse zu schreiben.
Dein Code würde meinen in 99% der Fälle einfach ersetzen, hätte ich jetzt nicht die Klasse.

Mein Code hat vielleicht insofern einen Vorteil, dass wenn man auf den Cache verzichtet, immer wieder der Enumerator genutzt wird. Dieser könnte also auch immer wieder neue Werte liefern. Das habe ich übrigens auch in meinen UnitTests überprüft.

PS: Die Idee mit der Repeat-Erweiterungsmethode gefällt mir. Diese werde ich wohl noch ergänzen :)
Anonymous2 schrieb am 1/30/2016:
Ohne Puffer bekommst du aber Probleme bei einer Enumeration, die nicht wieder von vorne beginnen kann.

Aber auch für die Methode ohne Puffer würde ich die Linq Erweiterung vorziehen. Die 40 Codezeilen sind (für mich zumindest) verständlicher als der Code deiner Klasse:
public static class IEnumerableExtension
{
public static IEnumerable<T> Repeat<T>(this IEnumerable<T> elements)
{
return elements.Repeat(true);
}

public static IEnumerable<T> Repeat<T>(this IEnumerable<T> elements, bool useBuffer)
{
if (useBuffer)
{
var buffer = new List<T>();

foreach (var elem in elements)
{
buffer.Add(elem);

yield return elem;
}

while (true)
{
foreach (var elem in buffer)
{
yield return elem;
}
}
}
else
{
while (true)
{
foreach (var elem in elements)
{
yield return elem;
}
}
}
}
}
Koopakiller schrieb am 1/30/2016:
Wenn eine Enumeration nur einmal stattfinden kann, sollte man das wissen bevor man sie durchläuft.

Einfacher ist dein Code natürlich, da hat meiner keine Chance. Wenigstens kann man hier sehen wie man selbst Enumeratoren schreiben kann.
 

Logge dich ein, um hier zu kommentieren!