Hallo,
ich habe ein wirkliches Basisproblem. Ich habe gerade meinen ersten eigenen Enumerator implementiert. Das mache ich, damit ich foreach von meiner Klasse benutzten kann.
Es ist eine klassisches Item/SubItem Implementierung
public interface IClass
{
public interface ISubClass : IEnumerable<IClass>
{
int Count { get; }
IClass this[int index] { get; }
void Add(string name);
void remove(string name);
void Clear();
}
}
internal class MyClass : IClass
{
public class SubClassImpl : IClass.ISubClass
{
public readonly List<MyClass> Items = []
public IEnumerator<IClass> GetEnumerator()
{
return new MyClassEnumerator(Items);
}
IEnumerator IEnumerable.GetEnumerator()
{
return new MyClassEnumerator(Items);
}
}
class MyClassEnumerator(List<MyClass> items) : IEnumerator<IClass>
{
readonly List<Class> items = items;
int position = -1;
public IClass Current => items[position];
object IEnumerator.Current => items[position];
public void Dispose()
{
}
public bool MoveNext()
{
if (position >= items.Count) return false;
position++;
return true;
}
public void Reset()
{
position = -1;
}
}
Wenn ich aber nun eine leere Liste habe, bekomme ich in der forEach Schleife eine Excpetion, da im enumerator auf Index -1 (der Positions Wert) zugegriffen wird.
Bei diesem Code
string[] x = [];
string s = "";
foreach (string xs in x)
s += xs;
bekomme ich aber keine Exception.
Wenn ich das richtig verstehe, was ich so gelesen habe, muss man hier mit einem Empty Enumerator arbeiten. Das Verstehe ich aber nicht richtig.
Wie muss ich meine enumeratoren verändern, damit ein foreach bei einer leeren Liste auch funktioniert?
Die Abfrage
if (position >= items.Count) return false;
wird bei einer leeren Liste nicht erfüllt (und daher der weitere Code ausgeführt, welcher dann true
zurückgibt und somit dann auf Current
zugegriffen wird → IndexOutOfRangeException
).
Und auch beim letzten Element-Index items.Count - 1
nicht.
Also muß es so aussehen:
if (position >= items.Count - 1) return false;
Danke.
Da hätte ich auch selber drauf kommen können.
Hallo,
du hättest auch einfach den Enumerator der Liste zurückgeben können:
public IEnumerator<IClass> GetEnumerator()
{
return Items.GetEnumerator();
}
Damit kannst du dir die Nachimplementierung sparen.
Grüße
Das ist ja noch besser.
Generell macht das sogar Sinn. Gut zu wissen.
Vielleicht dann noch mal eine Verständnisfrage:
Ich habe zuerst ja als Interface IEnumerable<IClass> angegeben. Visual Studio hat mir das meine ich so vorgeschlagen. Dann brauchte ich aber zwei Funktionen in meiner Implementierung
public IEnumerator<IClass> GetEnumerator() {...}
public IEnumerator GetEnumerator() {...}
Beide Funktionen hatten dann auch die gleiche Implementierung.
Ich habe es nun mal auf
public interface ISubClass : IEnumerable
geändert. Dann benötige ich auch nur noch die eine GetEnumerator Funktion.
Es funktioniert beides.
Kann es sein, dass eine "Vererbung" von IEnumberable<IClass> dann auch nur ein foreach für IClass zulässt? Ohne die Einschränkungen könnte ich aber auch noch über andere Typen enumerieren?
Da ich aber nur eine Liste in meiner Klasse habe, ist das ja ganz egal. Mit der Spezialisierung auf <IClass> musste ich ja auch noch eine "allgemeine Enumerator Funktion" (also IEnumerator GetEnumerator). Somit bringt es mir keine Vorteile, direkt das <IClass> anzugeben. Richtig?
Wenn ich allerdings in eine "Hyperklasse" zwei Listen mit unterschiedlichen Typen hätte, könnte ich IEnumerable<Typ1> und IEnumerable<Typ2> bei meiner Klasse als zu implementierende Interface angeben. Dann benötige ich zwei entsprechende GetEnumerator Funktionen, die verschiedene Enumeratoren zurückgeben würden. Somit könnte ich dann vom gleichen Objekt unterschiedlichen foreach mit unterschiedlichen Typen machen. Ein foreach (var x in objekt) dürfte dann aber nicht mehr funktionieren.
Nein, darum geht es nicht, und zwei gleiche Methoden mit unterschiedlichen Rückgabetypen sind auch gar nicht möglich.
IEnumerator IEnumerable.GetEnumerator()
ist eine Explicit Interface Implementation und diese wird aufgerufen, sobald man über das Interface die GetEnumerator
-Methode benutzt, z.B.
IEnumerable list = ...;
foreach (object x in list)
{
}
(bzw. bei dir über das davon abgeleitete Interface ISubClass
)
Man kann zwar mehrere explizite Interface-Implementierungen angeben, aber diese müssen dann auch explizit (daher der Name) über das zugehörige Interface aufgerufen werden.
Und bei der generischen Methode IEnumerator<IClass> GetEnumerator()
ist eben vom Compiler sichergestellt, daß auch nur IClass
(bzw. deren Basisklassen)-Objekte bei der Zuweisung akzeptiert werden.