Laden...

IEnumerator und foreach

Letzter Beitrag vor 15 Jahren 20 Posts 3.218 Views
IEnumerator und foreach

Nabend zusammen,

gerade komme ich absolut nicht weiter. Ich habe mir eine generische Klasse ArrayList<T> erstellt und will diese auch mit foreach laufbar machen. Allerdings brauche ich dazu ja eine GetEnumerator-Methode und ich finde absolut nichts, was auf meinen Fall passen würde. Es ist immer minimal anders und ich kriege es alleine nicht hin.

Bräuchte schnellstmöglichst einen Tipp und danke schonmal an alle Antworten.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;

namespace VokaForm
{
    class ArrayList<T> : IEnumerable<T>
    {
        T[] liste;
        int fuellungsGrad;
        int fuellungsGradMax;

        int schrittGroesse = 10;

        public ArrayList()
        {
            this.liste = new T[this.schrittGroesse];
            fuellungsGradMax += this.schrittGroesse;
            this.fuellungsGrad = 0;
        }

        public T this[int index]
        {
            get { return liste[index]; }

            set { liste[index] = value; }
        }

        public IEnumerator<T> GetEnumerator()
        {
            return new ArrayListEnum<T>(liste);
        }

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

        public void Add(T item)
        {
            if (this.fuellungsGrad == this.fuellungsGradMax)
            {
                T[] neueListe = new T[this.fuellungsGradMax + this.schrittGroesse];
                liste.CopyTo(neueListe, 0);
                liste = neueListe;
                neueListe = null;

                fuellungsGradMax += this.schrittGroesse;
            }
            this.liste[this.fuellungsGrad] = item;
            this.fuellungsGrad++;
        }

        public int FuellungsGrad
        {
            get { return this.fuellungsGrad; }
        }

        public int FuellungsGradMax
        {
            get { return this.fuellungsGradMax; }
        }

    }

    class ArrayListEnum<T> : IEnumerator
    {
        T[] liste;
        int position = -1;

        public ArrayListEnum(T[] list)
        {
            liste = list;
        }

        public bool MoveNext()
        {
            position++;
            return (position < liste.Length);
        }

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

        public object Current
        {
            get
            {
                try
                {
                    return liste[position];
                }
                catch (IndexOutOfRangeException)
                {
                    throw new InvalidOperationException();
                }
            }
        }
    }
}

also erstmal ist dir hoffentlich bewusst, dass es die Klasse, die du zu schreiben gedenkst, in .NET schon gibt und List<T> heißt, die ganz nebenbei noch mehrere nette Methoden zum Suchen anbietet und komplett optimiert ist wo es nur geht.

Was bei dir im Speziellen falsch läuft, ist das der Enumerator das komplette Array durchläuft, aber eigentlich nur die ersten paar Einträge zur Liste gehören, das heißt, du müsstest dem Enumerator schon noch irgendwie dir Information mitgeben, bis wo hin er laufen soll.

Und sowas:

try
                {
                    return liste[position];
                }
                catch (IndexOutOfRangeException)
                {
                    throw new InvalidOperationException();
                }

vermeide bitte, erstens brauchst du das an dieser Stelle gar nicht, weil position wird eh immer innerhalb eines gültigen Bereichs liegen, dafür hat MoveNext ja einen Rückgabewert...

Außerdem, wenn du schon deine eigene Liste implementierst, dann sollte die auch ICollection<T>, IList<T> und deren altertümliche Varianten ICollection und IList (aus Kompatibilitätsgründen) implementieren, wobei die nicht generischen nicht unbedingt.

Wobei eigene Collections prinzipiell sowieso von System.Collections.Generic.ObjectModel.Collection<T> erben sollten. Also du siehst anhand der Vielzahl der Konjunktive, dass diese Klasse produktiv nicht eingesetzt werden sollte.

Schonmal danke für die schnelle Antwort. Allerdings ist mir schon klar, dass ich auch genauso gut List<T> benutzen könnte, nur sehe ich es ja gerade als Aufgabe an, meine eigene Collection zu erstellen.

Und so wirklich rausziehen kann ich aus deinem Beitrag auch nur, dass ich noch mit angeben sollte, inwieweit die Liste schon gefüllt ist. Zugegeben, der untere Teil ist zu 95% kopiert und damit kriege ich zumindest nur noch eine Fehlermeldung:

Der Typ "VokaForm.ArrayListEnum<T>" kann nicht implizit in "System.Collections.Generic.IEnumerator<T>" konvertiert werden. Es ist bereits eine explizite Konvertierung vorhanden. (Möglicherweise fehlt eine Umwandlung.)

Und zwar kommt diese Fehlermeldung beim Aufruf der Klasse ArrayListEnum:

        public IEnumerator<T> GetEnumerator()
        {
            return new ArrayListEnum<T>(liste);
        }

Nur wie komme ich jetzt von der Klasse ArrayListEnum<T> zum Typ IEnumerator<T>?

ab C# 2.0 kann man die GetEnumerator<T> sehr einfach so implementieren:


public IEnumerator<T> GetEnumerator()
{
  foreach(T item in _myList)
  {
    yield return item;
  }
}

das wars.
Die nicht generische wird explizit implementiert und ruft nur die andere auf:

 
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
  return GetEnumerator();
}

edit:
_myList entspricht in deinem Beispiel T[] liste;

loop:
btst #6,$bfe001
bne.s loop
rts

Hallo bonzy,

Der Typ "VokaForm.ArrayListEnum<T>" kann nicht implizit in "System.Collections.Generic.IEnumerator<T>" konvertiert werden. Es ist bereits eine explizite Konvertierung vorhanden. (Möglicherweise fehlt eine Umwandlung.)

stimmt, denn:

class ArrayListEnum<T> : IEnumerator  // <== hier fehlt <T>  

herbivore

hallo,

wie bereits geschrieben, sollte man die vom .net FW bereitgestellten Klassen verwenden und hier keine eigenen Implementierung bereitstellen - das Klassendesign hat auch noch ein paar andere, nicht gerade "empfohlene Vorgangsweisen" (kannst den Code ja mal mit FxCop prüfen) aber egal, zu deiner Frage:


class MyList<T> : IEnumerable<T>
    {
        T[] internalList = new T[10];
        int pos = 0;

        public void Add(T item)
        {
            internalList[pos++] = item;
        }

        #region IEnumerable<T> Members

        public IEnumerator<T> GetEnumerator()
        {
            for (int i = 0; i < 10; i++)
            {
                yield return internalList[i];
            }
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return internalList.GetEnumerator();
        }

        #endregion
    }

        static void Main()
        {
            MyList<int> intList = new MyList<int>();
            intList.Add(123);
            intList.Add(456);

            foreach (var item in intList)
            {
                Console.WriteLine(item);
            }
}

Achtung, du schreibst in deinem Code:


T[] neueListe = new T[this.fuellungsGradMax + this.schrittGroesse];
liste.CopyTo(neueListe, 0);
liste = neueListe;
neueListe = null;

this.liste[this.fuellungsGrad] = item;

Nachdem es sich um ein Übungsbeispiel handelt: schau dir den Code nochmal an und überlege mal, was hier passieren könnte/wird.

edit: sorry, einfacher cast im IEnumerator<T> geht natürlich nicht, habs gefixt.

fg
hannes

Danke für die Antworten. Ich bin inzwischen auch darauf gekommen, dass man mit 2.0 auch yield return benutzen kann.

class ArrayListEnum<T> : IEnumerator  // <== hier fehlt <T>

Und hier habe ich ebenfalls auch schon ein <T> drangehangen, Problem war dabei nur, dass ich jetzt wiederrum Dispose() hinzufügen muss. Gibt es da bestimmte Schwierigkeiten oder Sachen, die ich beachten muss, oder kann ich einfach eine leere Dispose()-Methode implementieren?

Außerdem wird mir so eine Fehlermeldung bei Current angezeigt:

"VokaForm.ArrayListEnum<T>" implementiert den Schnittstellenmember "System.Collections.IEnumerator.Current" nicht. "VokaForm.ArrayListEnum<T>.Current" ist statisch, nicht öffentlich oder hat den falschen Rückgabewert.

Was genau muss ich ändern, damit ich das bereinigt kriege?

Also so gesehen klappt es jetzt alles, nur eben nicht auf die ursprüngliche Art und Weise aus 1.0, sondern mit der wesentlich kürzeren Version aus 2.0.

Achtung, du schreibst in deinem Code:

T[] neueListe = new T[this.fuellungsGradMax + this.schrittGroesse];  
liste.CopyTo(neueListe, 0);  
liste = neueListe;  
neueListe = null;  
  
this.liste[this.fuellungsGrad] = item;  

Nachdem es sich um ein Übungsbeispiel handelt: schau dir den Code nochmal an und überlege mal, was hier passieren könnte/wird.

Ich verstehe noch nicht ganz, was du mir damit sagen willst. Ich vermute gerade, dass ich beim Nullen der neueListe auch liste nulle, weil sie ja nicht kopiert, sondern nur zugewiesen wurde. Vielleicht wäre es besser, wenn ich hier den Code in eine Methode auslagere und den Rückgabewert sprich die neueListe auf liste lege.

Implementiere nicht IEnumerator<T> sondern IEnumerable<T>

loop:
btst #6,$bfe001
bne.s loop
rts

hi,

bei deinem programmkonstrukt bekommst du nach dem "neueListe = null;" und anschließenden Zugriff auf ein Listelement eine NullReferenceExection.

Außerdem ist es einfacher + sinnvoller, wie in meinem Beispiel angeführt und von "0815Coder" angemerkt, IEnumerable<T> zu implementieren.

fg
hannes

Also genau wie ich es mir gedacht habe. Aber da ich es ja nie zum Laufen bekommen habe, ist mir der Fehler bis dahin nicht aufgefallen.

Und mir ist auch klar, dass es einen simpleren und einfacherern Weg gibt. Aber ich mache das ja nicht, damit es am Ende klappt, sondern damit es am Ende klappt UND ich etwas dabei gelernt habe. Deswegen würde ich eben gerne das yield return weglassen und den Enumerator selber zu Fuß schreiben. Und dafür muss ich eben IEnumerator<T> implementieren, IEnumerable<T> habe ich ja so oder so in meinem ArrayList<T>.

Aktuell hänge ich nur noch an dem Current fest.

Hallo bonzy,

"VokaForm.ArrayListEnum<T>" implementiert den Schnittstellenmember "System.Collections.IEnumerator.Current" nicht. "VokaForm.ArrayListEnum<T>.Current" ist statisch, nicht öffentlich oder hat den falschen Rückgabewert.

Da IEnumerator<T> von IEnumerator "erbt", musst du eben nicht nur IEnumerator<T>.Current, sondern auch IEnumerator.Current implementieren.

Wenn du zwei gleichlautende Properties implementieren willst, musst du natürlich mindestens eine davon interface-explizit definieren.

Im Allgemeinen siehe [Hinweis] Syntaxfehler selbst lösen (Compilerfehlermeldungen).

Gibt es da bestimmte Schwierigkeiten oder Sachen, die ich beachten muss, oder kann ich einfach eine leere Dispose()-Methode implementieren? Dispose implementieren und verwenden

herbivore

Hallo bonzy
Current ist das aktuelle Element in foreach. Ich implementiere es immer so:

        public T Current { get { return list[current]; } }
        private int current; // Ev. auf das der untypisierten IEnumerator-Schnittstelle zugreifen

In MoveNext und Reset musst du dann nur noch den int-Wert verändern.

EDIT: Es gibt auch die IList<T> Schnittstelle, dann hast du alle nötigen Interfaces und praktische Erweiterungen gratis dazu.

Da IEnumerator<T> von IEnumerator "erbt", musst du eben nicht nur IEnumerator<T>.Current, sondern auch IEnumerator.Current implementieren.

Wenn du zwei gleichlautende Properties implementieren willst, musst du natürlich mindestens eine davon interface-explizit definieren.

        T IEnumerator<T>.Current
        {
            get { return liste[position]; }
        }

        public T Current
        {
            get { return liste[position]; }
        }

Soweit bin ich jetzt mit meinem Current, aber es klappt immer noch nicht. Es wird dieselbe Fehlermeldung angezeigt und ich weiß nicht, wie ich mit dieser Arbeiten soll. Current ist sowohl public als auch nicht statisch und gibt T, also meinen angegebenen Datentyp zurück. Wo könnte der Fehler liegen?

Current ist das aktuelle Element in foreach. Ich implementiere es immer so: [...]

Deine Lösung habe ich doch schon genauso verwendet, nur mit einem anderen Namen für die Int-Variable. .

Hallo bonzy,

du hast zweimal IEnumerator**<T>.Current implementiert (einmal interface-explizit und einmal interface-implizit). Du musst aber einmal IEnumerator.Current (sinnvollerweise interface-explizit) und einmal IEnumerator<T>**.Current (sinnvollerweise interface-implizit) implementieren.

herbivore

Das habe ich ebenfalls schon ausprobiert, nur bin ich da auf das Problem gestoßen, dass ein interface-explizites IEnumerator.Current nicht genommen wird. Egal ob mit System.Collections. davor oder auch ohne, es kommt immer die Fehlermeldung:

"IEnumerator.Current" in der expliziten Schnittstellendeklaration ist kein Member der Schnittstelle.

Aber gleichzeitig beschwehrt er sich ja noch drüber, dass Current eben nicht implementiert ist:

"VokaForm.ArrayListEnum<T>" implementiert den Schnittstellenmember "System.Collections.IEnumerator.Current" nicht.

So siehts aktuell aus:

        public T Current
        {
            get { return liste[position]; }
        }

        T IEnumerator.Current
        {
            get { return liste[position]; }
        }

Und entschuldigung für das Andauern dieses nervigen Themas, aber ich bin gerade völlig aufgeschmissen. Ich habe schon in einem dicken C#-Buch nachgelesen und dort steht auch nichts zu so einem Problem. Nur, dass ich z.B. abstract, virtual, override und static nicht benutzen darf sowie keinen Zugriffsmodifizierer. Aber das tue ich ja auch nicht.

Er implementiert System.Collections.IEnumerator.Current ja auch nicht sondern nur System.Collections.IEnumerator++**&lt;T&gt;**++.Current.

Das einfachste ist übrigens, in VS die Member automatisch hinzufügen zu lassen, dann passieren solche Fehler auch nicht.

So müsste es gehen (wie gesagt "müsste", ist ungetestet):

        public T Current
        {
            get { return liste[position]; }
        }

        object IEnumerator.Current
        {
            get { return liste[position]; }
        }

Ja hat geklappt. Die Sache war also nur, dass ich den richtigen Rückgabetyp von Current nicht eingesetzt habe.

Und was genau meinst du mit "Member automatisch hinzufügen zu lassen"? Habe da gerade keine Vorstellung von und würde gerne mal nachhaken. 😃

Hallo bonzy,

Die Sache war also nur, dass ich den richtigen Rückgabetyp von Current nicht eingesetzt habe.

was heißt "nur"? Dass es auf den Rückgabetyp unbedingt ankommt steht doch in der anderen Fehlermeldung:

"VokaForm.ArrayListEnum<T>" implementiert den Schnittstellenmember "System.Collections.IEnumerator.Current" nicht. "VokaForm.ArrayListEnum<T>.Current" ist statisch, nicht öffentlich oder hat den falschen Rückgabewert.

Deshalb nochmal als freundlicher, aber dennoch nachdrücklicher Hinweis: [Hinweis] Syntaxfehler selbst lösen (Compilerfehlermeldungen). Außerdem gibt es für Enumeratoren sicher genug Beispiele in der Doku, im Netz und vermutlich auch in deinem Buch, mit denen du das eigentlich hättest selber hinbekommen müssen.

Und was genau meinst du mit "Member automatisch hinzufügen zu lassen"?

See Sharp meint sicher die entsprechende Funktion im (Kontext-)Menü von Visual Studio.

herbivore

See Sharp meint sicher die entsprechende Funktion im (Kontext-)Menü von Visual Studio.

Genau. Hier eine 3-Bilder-Anleitung:

Deshalb nochmal als freundlicher, aber dennoch nachdrücklicher Hinweis:
>
. Außerdem gibt es für Enumeratoren sicher genug Beispiele in der Doku, im Netz und vermutlich auch in deinem Buch, mit denen du das eigentlich hättest selber hinbekommen müssen.

Es kommt jetzt vielleicht etwas dumm rüber, aber ich kann durchaus mit Syntaxfehlern umgehen. Nur wusste ich eben nicht, dass der Rückgabetyp der beiden Currents unterschiedlich ist. Wenn ich das gewusst hätte, wäre es mir auch sicher nicht schwer gefallen, aus dem T ein object zu machen. Aber trotzdem danke für den freundlichen, aber dennoch nachdrücklichen Hinweis. 😃
Und im Netz gibt es unzählige Beispiele für Enumeratoren, nur sind die alle nicht generisch und somit fehlte mir immer eine Information, um das Beispiel auf meine Lösung anzuwenden.

Genau. Hier eine 3-Bilder-Anleitung:

Alles klar, wusste nicht, dass es so leicht gewesen wäre. Habe bisher nur die automatische Umbenennung von Variablen benutzt.