2009-04-30 9 views
8

Bei der Implementierung eines grundlegenden Scheme-Interpreters in C# entdeckte ich zu meinem Entsetzen folgendes Problem:Warum kann IEnumerator nicht geklont werden?

IEnumerator hat keine Klon-Methode! (Genauer gesagt, IEnumerable kann mir keinen "klonbaren" Enumerator zur Verfügung stellen).

Was ich möchte:

interface IEnumerator<T> 
{ 
    bool MoveNext(); 
    T Current { get; } 
    void Reset(); 
    // NEW! 
    IEnumerator<T> Clone(); 
} 

Ich kann nicht mit einer Implementierung von IEnumerable kommen, die nicht eine effizienten klonbar IEnumerator (Vektoren, verkettete Listen wären in der Lage zu liefern, etc. alles wäre in der Lage zu eine triviale Implementierung von IEnumerator's Clone() wie oben spezifiziert ... es wäre einfacher, als eine Reset() -Methode bereitzustellen!).

Das Fehlen der Clone-Methode bedeutet, dass jede funktionale/rekursive Idiom der Aufzählung über eine Sequenz nicht funktioniert.

Es bedeutet auch, dass ich nicht "nahtlos" IEnumerables wie Lisp "Listen" (für die Sie Auto/Cdr verwenden, um rekursiv zu nummerieren) machen können. d.h. die einzige Implementierung von "(cdr einige IEnumerable)" wäre beklagenswert ineffizient.

Kann jemand ein realistisches, nützliches Beispiel für ein IEnumerable-Objekt vorschlagen, das nicht in der Lage wäre, eine effiziente "Clone()" -Methode bereitzustellen? Ist es ein Problem mit dem "Yield" -Konstrukt?

Kann jemand einen Workaround vorschlagen?

+0

Ich bin mir nicht sicher, warum Sie einen klonbaren IEnumerator möchten, aber wäre dies eine klonbare IEnumerable-Hilfe? – Lucas

+1

FYI, Es kann geklont werden. Meine Antwort vor langer Zeit zeigt, wie. Es klont tatsächlich den IEnumerator und nicht die daraus erstellten Werte. – BenMaddox

Antwort

23

Die Logik ist unerbittlich! IEnumerable unterstützt Clone nicht, und Sie benötigen Clone, Sie sollten also nicht IEnumerable verwenden.

Oder genauer gesagt, Sie sollten es nicht als grundlegende Grundlage für die Arbeit an einem Scheme-Interpreter verwenden. Warum nicht stattdessen eine triviale unveränderliche verknüpfte Liste erstellen?

public class Link<TValue> 
{ 
    private readonly TValue value; 
    private readonly Link<TValue> next; 

    public Link(TValue value, Link<TValue> next) 
    { 
     this.value = value; 
     this.next = next; 
    } 

    public TValue Value 
    { 
     get { return value; } 
    } 

    public Link<TValue> Next 
    { 
     get { return next; } 
    } 

    public IEnumerable<TValue> ToEnumerable() 
    { 
     for (Link<TValue> v = this; v != null; v = v.next) 
      yield return v.value; 
    } 
} 

Beachten Sie, dass die ToEnumerable Methode Sie die bequeme Nutzung in der Standard-C# nachgibt.

Um Ihre Frage zu beantworten:

Kann mir jemand eine realistische, nützlich, beispielsweise eines IEnumerable Objekt vorschlagen, die bieten eine effiziente „Clone()“ Methode nicht in der Lage sein würde? Gibt es ein Problem mit das "Yield" -Konstrukt?

Ein IEnumerable kann überall in der Welt für seine Daten gehen. Hier ist ein Beispiel, die Linien von der Konsole lautet:

IEnumerable<string> GetConsoleLines() 
{ 
    for (; ;) 
     yield return Console.ReadLine(); 
} 

Es gibt zwei Probleme: erstens eine Clone Funktion wäre nicht besonders einfach zu schreiben (und Reset wäre sinnlos). Zweitens ist die Reihenfolge unendlich - was absolut zulässig ist. Sequenzen sind faul.

Ein weiteres Beispiel:

IEnumerable<int> GetIntegers() 
{ 
    for (int n = 0; ; n++) 
     yield return n; 
} 

beide Für diese Beispiele der „Abhilfe“ Sie akzeptiert haben würde nicht viel nützen, weil es nur den verfügbaren Speicher für immer erschöpfen würde oder aufzuhängen. Aber das sind absolut gültige Beispiele für Sequenzen.

Um C# - und F # -Sequenzen zu verstehen, müssen Sie Listen in Haskell betrachten, nicht Listen in Scheme.

Falls Sie denken, Sie die unendliche Sachen ein roter Hering ist, wie etwa die Bytes von einem Socket zu lesen:

IEnumerable<byte> GetSocketBytes(Socket s) 
{ 
    byte[] buffer = new bytes[100]; 
    for (;;) 
    { 
     int r = s.Receive(buffer); 
     if (r == 0) 
      yield break; 

     for (int n = 0; n < r; n++) 
      yield return buffer[n];  
    } 
} 

Wenn es eine bestimmte Anzahl von Bytes ist, die Buchse herabgesandt wird, wird dies nicht sein unendliche Sequenz. Und es wäre sehr schwierig, Clone dafür zu schreiben. Wie würde der Compiler die IEnumerable-Implementierung generieren, um dies automatisch zu tun?

Sobald ein Klon erstellt wurde, müssten beide Instanzen nun von einem gemeinsamen Puffersystem aus arbeiten. Es ist möglich, aber in der Praxis wird es nicht benötigt - so werden diese Sequenzen nicht verwendet. Sie behandeln sie rein "funktional", wie Werte, und wenden Filter rekursiv auf sie an, anstatt sich "zwingend" an einen Ort innerhalb der Sequenz zu erinnern.Es ist ein wenig sauberer als Low-Level car/cdr Manipulation.

Weitere Frage:

Ich frage mich, was ist das niedrigste Niveau "primitive (s)" Ich müsste so dass alles, was ich kann mit einem IEnumerable in meinen Scheme-Interpreter tun will könnte im Schema eher als als eingebaut implementiert werden.

Die kurze Antwort würde ich in Abelson and Sussman und insbesondere the part about streams suchen. IEnumerable ist ein Stream, keine Liste. Und sie beschreiben, wie Sie spezielle Versionen von Map, Filter, Accumulate usw. benötigen, um mit ihnen zu arbeiten. Sie kommen auch auf die Idee der Vereinigung von Listen und Streams in Abschnitt 4.2.

+0

Ja, ich weiß, wie man eine verkettete Liste schreibt. Ich versuche zu vermeiden, dass alle C# -Codes (insbesondere benutzerdefinierte Funktionen) alle IEnumerables im Vorfeld in verknüpfte Listen konvertieren müssen. Mir ist klar, dass das nicht möglich ist ... aber jetzt frage ich mich: Warum haben sie IEnumerator nicht klonbar gemacht? –

+0

Hinweis: Ich habe Ihre Antwort trotzdem gewählt - dank der Eingabe –

+0

Siehe aktualisierte Antwort für das "warum", auch ein Grund, warum die "make a copy" -Arbeitsumgehung nicht ganz richtig ist. –

4

Um dieses Problem zu umgehen, können Sie problemlos eine Erweiterungsmethode für IEnumerator erstellen, die das Klonen durchgeführt hat. Erstellen Sie einfach eine Liste aus dem Enumerator und verwenden Sie die Elemente als Mitglieder.

Sie würden die Streaming-Funktionen eines Enumerators verlieren, obwohl - da Sie neu sind "Klon" würde der erste Enumerator vollständig auswerten.

+0

beste, nützlichste Antwort bisher ... aber immer noch möchte ein Beispiel dafür, warum IEnumerator nicht klonbar machen könnte ... (das Bit in Fett in meiner Frage hervorgehoben). –

+0

Wie ich bereits erwähnt habe - jeder Fall, in dem Sie das Streaming (verzögerte Ausführung) eines IEnumerator nutzen, würde die Vorteile brechen oder zumindest verlieren, sobald Sie den Enumerator geklont haben. Es ist mehr eine Frage der Kosten und Vorteile der Bereitstellung von Klonen. –

+0

ja nein ich realisiere die Kosten in Ihrer Lösung .. der Punkt ist, gibt es eine realistische * Implementierung * wenn IEnumerable/IEnumerator, die nicht in der Lage wäre, eine effiziente Clone-Methode zu bieten ...? –

0

Warum dies nicht als eine Erweiterungsmethode:

public static IEnumerator<T> Clone(this IEnumerator<T> original) 
{ 
    foreach(var v in original) 
     yield return v; 
} 

Dies würde im Grunde erstellen und einen neuen Enumerator zurück, ohne das Original vollständig zu bewerten.

Edit: Ja, ich falsch gelesen. Paul hat Recht, das würde nur mit IEnumerable funktionieren.

+0

Das wird nicht funktionieren - Sie können nicht über einen IEnumerator foreach, oder? Nur über IEnumerable ... –

+0

Dieser Code wird nicht einmal kompiliert, Sie können nicht über IEnumerator Original foreach und Sie können nicht Ausbeute verwenden, um IEnumerator zurückzugeben. Wahrscheinlich meintest du IEnumerable in beiden Fällen. – Lucas

-2

Es gibt bereits eine Möglichkeit, einen neuen Enumerator zu erstellen - genauso wie Sie den ersten erstellt haben: IEnumerable.GetEnumerator. Ich bin mir nicht sicher, warum Sie einen anderen Mechanismus brauchen, um das Gleiche zu tun.

Und im Geiste der DRY principle, bin ich neugierig, warum Sie die Verantwortung für die Erstellung neuer IEnumerator-Instanzen sowohl in Ihrer Enumerable und Ihre Enumerator-Klassen dupliziert werden sollen. Sie würden den Enumerator dazu zwingen, einen zusätzlichen Status zu behalten, der über das hinausgeht, was erforderlich ist.

Stellen Sie sich beispielsweise einen Enumerator für eine verkettete Liste vor. Für die grundlegende Implementierung von IEnumerable muss diese Klasse nur einen Verweis auf den aktuellen Knoten beibehalten. Um Ihren Klon zu unterstützen, müssen Sie aber auch einen Verweis auf den Kopf der Liste behalten - etwas, für das er ansonsten nicht nützlich ist *. Warum sollten Sie diesen zusätzlichen Status dem Enumerator hinzufügen, wenn Sie einfach zur Quelle (IEnumerable) gehen und einen anderen Enumerator abrufen können?

Und warum sollten Sie die Anzahl der Codepfade verdoppeln, die Sie testen müssen? Jedes Mal, wenn Sie einen neuen Weg zur Herstellung eines Objekts machen, fügen Sie Komplexität hinzu.

* Sie würden auch den Kopfzeiger benötigen, wenn Sie Reset implementiert haben, aber according to the docs, Reset ist nur für COM-Interop verfügbar, und Sie können eine NotSupportedException auslösen.

+1

Ein geklonter Enumerator zeigt nicht auf das erste Element in der Sequenz. Es würde auf das aktuelle Element des ursprünglichen Enumerators zeigen. Der Zweck wäre, sich an die aktuelle Position erinnern zu können. –

+1

Einige Enumeratoren konnten ihre aktuelle Position klonen, viele jedoch nicht. Vorwärts-nur Datenbank-Cursor, zum Beispiel; oder irgendetwas, das von einem Netzwerkstrom oder einer Pipe liest. Selbst etwas, das wie ein Dateistream von einer lokalen Ressource liest, würde viel Arbeit erfordern, um klonbar zu machen, da ein Datei-Handle nur eine aktuelle Position hat. –

0

Dies könnte helfen. Es braucht einige Codes die Dispose() auf dem IEnumerator zu nennen:

class Program 
{ 
    static void Main(string[] args) 
    { 
     //var list = MyClass.DequeueAll().ToList(); 
     //var list2 = MyClass.DequeueAll().ToList(); 

     var clonable = MyClass.DequeueAll().ToClonable(); 


     var list = clonable.Clone().ToList(); 
     var list2 = clonable.Clone()ToList(); 
     var list3 = clonable.Clone()ToList(); 
    } 
} 

class MyClass 
{ 
    static Queue<string> list = new Queue<string>(); 

    static MyClass() 
    { 
     list.Enqueue("one"); 
     list.Enqueue("two"); 
     list.Enqueue("three"); 
     list.Enqueue("four"); 
     list.Enqueue("five"); 
    } 

    public static IEnumerable<string> DequeueAll() 
    { 
     while (list.Count > 0) 
      yield return list.Dequeue(); 
    } 
} 

static class Extensions 
{ 
    public static IClonableEnumerable<T> ToClonable<T>(this IEnumerable<T> e) 
    { 
     return new ClonableEnumerable<T>(e); 
    } 
} 

class ClonableEnumerable<T> : IClonableEnumerable<T> 
{ 
    List<T> items = new List<T>(); 
    IEnumerator<T> underlying; 

    public ClonableEnumerable(IEnumerable<T> underlying) 
    { 
     this.underlying = underlying.GetEnumerator(); 
    } 

    public IEnumerator<T> GetEnumerator() 
    { 
     return new ClonableEnumerator<T>(this); 
    } 

    IEnumerator IEnumerable.GetEnumerator() 
    { 
     return this.GetEnumerator(); 
    } 

    private object GetPosition(int position) 
    { 
     if (HasPosition(position)) 
      return items[position]; 

     throw new IndexOutOfRangeException(); 
    } 

    private bool HasPosition(int position) 
    { 
     lock (this) 
     { 
      while (items.Count <= position) 
      { 
       if (underlying.MoveNext()) 
       { 
        items.Add(underlying.Current); 
       } 
       else 
       { 
        return false; 
       } 
      } 
     } 

     return true; 
    } 

    public IClonableEnumerable<T> Clone() 
    { 
     return this; 
    } 


    class ClonableEnumerator<T> : IEnumerator<T> 
    { 
     ClonableEnumerable<T> enumerable; 
     int position = -1; 

     public ClonableEnumerator(ClonableEnumerable<T> enumerable) 
     { 
      this.enumerable = enumerable; 
     } 

     public T Current 
     { 
      get 
      { 
       if (position < 0) 
        throw new Exception(); 
       return (T)enumerable.GetPosition(position); 
      } 
     } 

     public void Dispose() 
     { 
     } 

     object IEnumerator.Current 
     { 
      get { return this.Current; } 
     } 

     public bool MoveNext() 
     { 
      if(enumerable.HasPosition(position + 1)) 
      { 
       position++; 
       return true; 
      } 
      return false; 
     } 

     public void Reset() 
     { 
      position = -1; 
     } 
    } 


} 

interface IClonableEnumerable<T> : IEnumerable<T> 
{ 
    IClonableEnumerable<T> Clone(); 
} 
2

Wenn Sie das Original enumerator gehen lassen kann, dh. Wenn Sie es nicht mehr verwenden, können Sie eine "Klon" -Funktion implementieren, die den ursprünglichen Enumerator verwendet und sie als Quelle für einen oder mehrere Enumeratoren verwendet.

Mit anderen Worten, Sie könnten so etwas wie dieses bauen:

IEnumerable<String> original = GetOriginalEnumerable(); 
IEnumerator<String>[] newOnes = original.GetEnumerator().AlmostClone(2); 
                 ^- extension method 
                 produce 2 
                 new enumerators 

Diese intern könnte die ursprüngliche enumerator teilen, und eine verknüpfte Liste, den Überblick über die aufgezählten Werte zu halten.

Diese für erlauben würde:

  • unendliche Folgen, solange beide Aufzählungen Fortschritte nach vorne (die verknüpfte Liste so geschrieben werden würde, dass, wenn beide Aufzählungen einen bestimmten Punkt passiert haben, können diejenigen GC'ed werden)
  • Faule Aufzählung, die erste der beiden Aufzählungen, die einen Wert benötigen, die von der ursprünglichen enumerator abgerufen wurde noch nicht, würde es es erhalten und es in der verknüpften Liste speichern, bevor es

Problem hier nachgebend ist natürlich th Es würde immer noch viel Speicher benötigen, wenn einer der Zähler weit vor dem anderen steht.

Hier ist der Quellcode. Wenn Sie Subversion verwenden, können Sie die Visual Studio 2008-Lösungsdatei mit einer Klassenbibliothek mit dem unten stehenden Code sowie ein separates Komponententestprojekt herunterladen.

Repository: http://vkarlsen.serveftp.com:81/svnStackOverflow/SO847655
Benutzername und Passwort ist "Gast", ohne die Anführungszeichen.

Beachten Sie, dass dieser Code überhaupt nicht Thread-sicher ist.

public static class EnumeratorExtensions 
{ 
    /// <summary> 
    /// "Clones" the specified <see cref="IEnumerator{T}"/> by wrapping it inside N new 
    /// <see cref="IEnumerator{T}"/> instances, each can be advanced separately. 
    /// See remarks for more information. 
    /// </summary> 
    /// <typeparam name="T"> 
    /// The type of elements the <paramref name="enumerator"/> produces. 
    /// </typeparam> 
    /// <param name="enumerator"> 
    /// The <see cref="IEnumerator{T}"/> to "clone". 
    /// </param> 
    /// <param name="clones"> 
    /// The number of "clones" to produce. 
    /// </param> 
    /// <returns> 
    /// An array of "cloned" <see cref="IEnumerator[T}"/> instances. 
    /// </returns> 
    /// <remarks> 
    /// <para>The cloning process works by producing N new <see cref="IEnumerator{T}"/> instances.</para> 
    /// <para>Each <see cref="IEnumerator{T}"/> instance can be advanced separately, over the same 
    /// items.</para> 
    /// <para>The original <paramref name="enumerator"/> will be lazily evaluated on demand.</para> 
    /// <para>If one enumerator advances far beyond the others, the items it has produced will be kept 
    /// in memory until all cloned enumerators advanced past them, or they are disposed of.</para> 
    /// </remarks> 
    /// <exception cref="ArgumentNullException"> 
    /// <para><paramref name="enumerator"/> is <c>null</c>.</para> 
    /// </exception> 
    /// <exception cref="ArgumentOutOfRangeException"> 
    /// <para><paramref name="clones"/> is less than 2.</para> 
    /// </exception> 
    public static IEnumerator<T>[] Clone<T>(this IEnumerator<T> enumerator, Int32 clones) 
    { 
     #region Parameter Validation 

     if (Object.ReferenceEquals(null, enumerator)) 
      throw new ArgumentNullException("enumerator"); 
     if (clones < 2) 
      throw new ArgumentOutOfRangeException("clones"); 

     #endregion 

     ClonedEnumerator<T>.EnumeratorWrapper wrapper = new ClonedEnumerator<T>.EnumeratorWrapper 
     { 
      Enumerator = enumerator, 
      Clones = clones 
     }; 
     ClonedEnumerator<T>.Node node = new ClonedEnumerator<T>.Node 
     { 
      Value = enumerator.Current, 
      Next = null 
     }; 

     IEnumerator<T>[] result = new IEnumerator<T>[clones]; 
     for (Int32 index = 0; index < clones; index++) 
      result[index] = new ClonedEnumerator<T>(wrapper, node); 
     return result; 
    } 
} 

internal class ClonedEnumerator<T> : IEnumerator<T>, IDisposable 
{ 
    public class EnumeratorWrapper 
    { 
     public Int32 Clones { get; set; } 
     public IEnumerator<T> Enumerator { get; set; } 
    } 

    public class Node 
    { 
     public T Value { get; set; } 
     public Node Next { get; set; } 
    } 

    private Node _Node; 
    private EnumeratorWrapper _Enumerator; 

    public ClonedEnumerator(EnumeratorWrapper enumerator, Node firstNode) 
    { 
     _Enumerator = enumerator; 
     _Node = firstNode; 
    } 

    public void Dispose() 
    { 
     _Enumerator.Clones--; 
     if (_Enumerator.Clones == 0) 
     { 
      _Enumerator.Enumerator.Dispose(); 
      _Enumerator.Enumerator = null; 
     } 
    } 

    public T Current 
    { 
     get 
     { 
      return _Node.Value; 
     } 
    } 

    Object System.Collections.IEnumerator.Current 
    { 
     get 
     { 
      return Current; 
     } 
    } 

    public Boolean MoveNext() 
    { 
     if (_Node.Next != null) 
     { 
      _Node = _Node.Next; 
      return true; 
     } 

     if (_Enumerator.Enumerator.MoveNext()) 
     { 
      _Node.Next = new Node 
      { 
       Value = _Enumerator.Enumerator.Current, 
       Next = null 
      }; 
      _Node = _Node.Next; 
      return true; 
     } 

     return false; 
    } 

    public void Reset() 
    { 
     throw new NotImplementedException(); 
    } 
} 
+0

Clever ... also ist es wie eine faul bewertete Version von Reed Copseys Antwort (also verwendet es nur alle Elemente der Liste, wenn Sie es tatsächlich so benutzen). –

+0

Richtig, ich werde etwas später eine Beispielimplementierung posten, um eine Klasse zu unterrichten. –

+0

Wird sehr daran interessiert sein, dies zu sehen - ich begann das gleiche für meine Antwort neu zu schreiben, aber nie fertig. –

1

Diese verwendet Reflektion zum Erstellen einer neuen Instanz und legt dann die Werte für die neue Instanz fest. Ich fand auch dieses Kapitel aus C# in Depth sehr nützlich. Iterator block implementation details: auto-generated state machines

static void Main() 
{ 
    var counter = new CountingClass(); 
    var firstIterator = counter.CountingEnumerator(); 
    Console.WriteLine("First list"); 
    firstIterator.MoveNext(); 
    Console.WriteLine(firstIterator.Current); 

    Console.WriteLine("First list cloned"); 
    var secondIterator = EnumeratorCloner.Clone(firstIterator); 

    Console.WriteLine("Second list"); 
    secondIterator.MoveNext(); 
    Console.WriteLine(secondIterator.Current); 
    secondIterator.MoveNext(); 
    Console.WriteLine(secondIterator.Current); 
    secondIterator.MoveNext(); 
    Console.WriteLine(secondIterator.Current); 

    Console.WriteLine("First list"); 
    firstIterator.MoveNext(); 
    Console.WriteLine(firstIterator.Current); 
    firstIterator.MoveNext(); 
    Console.WriteLine(firstIterator.Current); 
} 

public class CountingClass 
{ 
    public IEnumerator<int> CountingEnumerator() 
    { 
     int i = 1; 
     while (true) 
     { 
      yield return i; 
      i++; 
     } 
    } 
} 

public static class EnumeratorCloner 
{ 
    public static T Clone<T>(T source) where T : class, IEnumerator 
    { 
     var sourceType = source.GetType().UnderlyingSystemType; 
     var sourceTypeConstructor = sourceType.GetConstructor(new Type[] { typeof(Int32) }); 
     var newInstance = sourceTypeConstructor.Invoke(new object[] { -2 }) as T; 

     var nonPublicFields = source.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance); 
     var publicFields = source.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance); 
     foreach (var field in nonPublicFields) 
     { 
      var value = field.GetValue(source); 
      field.SetValue(newInstance, value); 
     } 
     foreach (var field in publicFields) 
     { 
      var value = field.GetValue(source); 
      field.SetValue(newInstance, value); 
     } 
     return newInstance; 
    } 
} 

Diese Antwort auch auf die folgenden Fragen verwendet wurde Is it possible to clone an IEnumerable instance, saving a copy of the iteration state?

+1

Dies funktioniert nicht in allen Fällen und beruht auf undokumentiertem Verhalten (möglicherweise jetzt, aber möglicherweise nicht später). Siehe diesen Kommentar: http://stackoverflow.com/questions/807991/why-cant-ienumerators-be-cloned#comment621350_808121 Wenn Sie jedoch den Enumerator selbst erstellt haben, können Sie nicht nur dessen Sicherheit überprüfen, um einen Memberwise-Klon zu erstellen , aber Sie können auch eine geeignete Klonmethode erstellen. – Brent

0

Der Zweck der „clonable“ Enumeratoren hauptsächlich speichern Iteration Position in der Lage ist und später darauf zurückkommen können. Das bedeutet, dass der iterierte Container eine reichere Schnittstelle als nur IEnumerable bereitstellen muss. Es ist eigentlich etwas zwischen IEnumerable und IList. Bei Verwendung von IList können Sie einfach den Ganzzahl-Index als Enumerator verwenden oder eine einfache unveränderliche Wrapping-Klasse erstellen, die einen Verweis auf die Liste und die aktuelle Position enthält.

Wenn Ihr Container keinen Direktzugriff unterstützt und nur vorwärts iteriert werden kann (wie eine unidirektionale verknüpfte Liste), muss er mindestens die Möglichkeit bieten, das nächste Element zu erhalten, einen Verweis auf den vorherigen oder auf eine Iteration Geben Sie an, "dass Sie in Ihrem Iterator halten können. So kann die Schnittstelle wie folgt aussehen:

interface IIterable<T> 
{ 
    IIterator<T> GetIterator(); // returns an iterator positioned at start 
    IIterator<T> GetNext(IIterator<T> prev); // returns an iterator positioned at the next element from the given one 
} 

interface IIterator<T> 
{ 
    T Current { get; } 
    IEnumerable<T> AllRest { get; } 
} 

Beachten Sie, dass der Iterator ist unveränderlich, kann es nicht sein „vorwärts bewegt“, wir können nur unser iterable Container fragen Sie uns einen neuen Iterator auf das Verzeichnis geben nächste Position. Der Vorteil davon ist, dass Sie Iteratoren beliebig lange speichern können, zum Beispiel einen Stapel von Iteratoren haben und bei Bedarf zur zuvor gespeicherten Position zurückkehren können. Sie können die aktuelle Position für die spätere Verwendung speichern, indem Sie sie einer Variablen zuweisen, genau wie bei einem Integer-Index.

Die Eigenschaft AllRest kann nützlich sein, wenn Sie von der angegebenen Position bis zum Ende des Containers mit den Standardsprachen-Iterationsfunktionen wie foraech oder LinQ iterieren müssen. Es wird die Iteratorposition nicht ändern (denken Sie daran, unser Iterator ist unveränderlich). Die Implementierung kann wiederholt GetNext und yleid return.

Die GetNext Methode kann tatsächlich ein Teil von Iterator selbst sein, wie folgt aus:

interface IIterable<T> 
{ 
    IIterator<T> GetIterator(); // returns an iterator positioned at start 
} 

interface IIterator<T> 
{ 
    T Current { get; } 
    IIterator<T> GetNext { get; } // returns an iterator positioned at the next element from the given one 
    IEnumerable<T> AllRest { get; } 
} 

Das ist so ziemlich das gleiche. Die Logik zum Bestimmen des nächsten Zustands wird gerade von der Containerimplementierung in die Implementierung des Iterators verschoben. Beachten Sie, dass der Iterator immer noch unveränderlich ist. Sie können es nicht "vorwärts bewegen", Sie können nur einen anderen bekommen und auf das nächste Element zeigen.

Verwandte Themen