Laden...

IEnumerator und IEnumerable

Erstellt von steeveKa1 vor 3 Monaten Letzter Beitrag vor 3 Monaten 415 Views
S
steeveKa1 Themenstarter:in
1 Beiträge seit 2023
vor 3 Monaten
IEnumerator und IEnumerable

Hallo,

ich beschäftige mich gerade mit den IEnumerator und IEnumerable Interfaces. Leider verstehe ich nicht ganz wie das IEnumerator-Interface durch die GetEnumerator()-Methode implementiert wird. Als Beispiel habe ich folgenden Code am Ende angehängt.

Soweit ich weiß, hat die Klasse Array ja bereits das Interface IEnumerable implementiert, weshalb ich über das Array-Objekt myArray auf die GetEnumerator()-Methode zugreifen kann. Unklar ist jedoch wie myEnumerator gebildet wird, bzw. um was genau es sich dabei handelt. Es heißt doch immer, dass man keine Objekte von Interfaces erstellen kann. Warum ist aber myEnumerator nun ein Objekt vom Interface IEnumerator? Ich weiß wie man Interfaces mit Hilfe einer Klasse implementiert und auch was ein Interface ist. Trotzdem bleibt mir diese Instanziierung schleierhaft.

Wäre für jegliche Hilfe dankbar.

var myArray = new int[] { 1, 2, 3 };

var myEnumerator = myArray.GetEnumerator();

while (myEnumerator.MoveNext())
{
    Console.WriteLine(myEnumerator.Current);
    myEnumerator.Reset();
}
4.921 Beiträge seit 2008
vor 3 Monaten

Hallo und willkommen,

als Anwender der GetEnumerator-Methode wird nur mit dem Interface IEnumerator gearbeitet (wie in deinem Code), die Methode selbst gibt jedoch (je nach Klasse) ein dafür passendes Objekt einer davon abgeleiteten/implementierten Klasse zurück. Im Falle vom Array die Klasse ArrayEnumerator welche von Array.GetEnumerator() benutzt wird (bzw. für generische Arrays SZGenericArrayEnumerator<T>).

Du kannst ja mal in deinem Code myEnumerator.GetType()ausgeben lassen.

16.788 Beiträge seit 2008
vor 3 Monaten

Zitat von steeveKa1

Warum ist aber myEnumerator nun ein Objekt vom Interface IEnumerator?

Ist er nicht. Es ist ein Interface. Das siehst Du auch, wenn Du das Interface verwendest statt var.
var ist bequem, aber leider verschleiert das viel. Wenn Du Dir also nicht sicher bist: verzichte auf var.

Du kannst einfach in den Quellcode schauen, was für einen Typ zu bekommst.
Im Falle von Array kann das unterschiedlich sein

// https://github.com/microsoft/referencesource/blob/51cf7850defa8a17d815b4700b67116e3fa283c2/mscorlib/system/array.cs#L1264C7-L1264C7

public IEnumerator GetEnumerator()
{
    int lowerBound = GetLowerBound(0);
    if (Rank == 1 && lowerBound == 0)
        return new SZArrayEnumerator(this);
    else
        return new ArrayEnumerator(this, lowerBound, Length);
}

Edit: Formulerungskorrektur.

4.921 Beiträge seit 2008
vor 3 Monaten

Zitat von Abt

Zitat von steeveKa1

Warum ist aber myEnumerator nun ein Objekt vom Interface IEnumerator?

Ist er nicht. Es ist ein Inteface. Das siehst Du auch, wenn Du den konkreten Typ verwendest statt var.
var ist bequem, aber leider verschleiert das viel. Wenn Du Dir also nicht sicher bist: verzichte auf var.

Das liest sich jetzt aber so, als ob man jetzt statt var den konkreten Typ hinschreiben soll, aber das geht natürlich nicht, sondern der Rückgabetyp ist ja explizit IEnumerator und der konkrete Typ wird erst zur Laufzeit erzeugt.

T
73 Beiträge seit 2004
vor 3 Monaten

Ein IEnumerable<T> definiert einen Typ der lediglich eine Funktion enthält, nämlich GetEnumerator() und liefert ein Objekt vom Typ IEnumerator<T>.

Ein IEnumerator<T> ist ein Typ der eine Eigenschaft sowie 2 Methoden enthält:

  • T Current: liefert einen Wert vom Typen T
  • bool MoveNext(): bewegt einen internen Index auf das nächste Element, liefert TRUE solange es Elemente gibt.
  • void Reset(): setzt den Index aufs 1. Element

Dies ist in .NET eine ziemlich geradlinige Implementation des Iterator-Patterns.

Jede Klasse, die einen IEnumerable implementiert, kann in C# mittel der foreach-Schleife iteriert werden. Das ist praktisch, um die Kapselung interner Datenstrukturen/Listen vor dem Konsumenten zu verbergen und entspricht dem Paradigma des "Information Hiding".

p.s. dein Beispiel oben kann nicht funktionieren, da Reset() VOR der While-Schleife stehen muss!

125 Beiträge seit 2023
vor 3 Monaten

Zitat von tomschrot

  • void Reset(): setzt den Index aufs 1. Element

In der Original Beschreibung steht

Sets the enumerator to its initial position, which is before the first element in the collection.

Der Index steht also nach Reset() vor dem ersten Element und nicht darauf.

Hat die Blume einen Knick, war der Schmetterling zu dick.

T
73 Beiträge seit 2004
vor 3 Monaten

@BlonderHans: ja klar, damit MoveNext() nach Reset() aufs 1 (!!!) Element zeigt:

Das Iterator-Pattern verwendet eine Annehmende-Schleife:

        var enumerator = collection?.GetEnumerator ();
        enumerator?.Reset();
        
        while ( enumerator?.MoveNext() ?? false )
        {
            var value = enumerator.Current;
            ...
        }
125 Beiträge seit 2023
vor 3 Monaten

Dann hast du es ja jetzt verstanden

Hat die Blume einen Knick, war der Schmetterling zu dick.

16.788 Beiträge seit 2008
vor 3 Monaten

Diese ganzen doppelten und dreifachen Null-Checks kann man einfach mit einem if ausmerzen.
Syntax Sugar sollte schon überlegt angewendet werden. Wendet man Syntax Sugar hingegen inflationär an, ist das nicht nur für den Clean Code scheisse, sondern auch in den aller meisten Fällen ziemlich kontraproduktiv für die Performance.

T
73 Beiträge seit 2004
vor 3 Monaten

@Abt: Wenn das alles so doof ist, frage ich mich, warum das dann in C# eingebaut wird? Das C# Team bei MS ist ziemlich konservativ und neuer Syntax kommt nur in kleinen Dosen. Die wissen schon ziemlich genau, was sie tun.

Die NULL Operatoren sind schon sinnvoll, wenn sie richtig eingesetzt werden. Außerdem unterscheidet der Compiler genau zwischen NULLABLE und NOT NULLABLE Code. Dazu enthält csproj <Nullable>enable</Nullable> oder im Code #pragma nullable enable / disable / restore.

2.071 Beiträge seit 2012
vor 3 Monaten

Wenn das alles so doof ist, frage ich mich, warum das dann in C# eingebaut wird?

Es ist nicht doof, aber man muss es - wie Abt schreibt - überlegt verwenden.

Microsoft baut viele Funktionen ein, wie Du sie benutzt, ist deine Sache.
Und sie haben auch eine Funktion eingebaut, dass man vor dem ganzen Code auf null prüfen und dann ein Default-Verhalten implementieren kann, das macht die ganzen null-Checks danach überflüssig.
Der Null-conditional Operator lohnt sich besonders dann, wenn man nur eine Null-Prüfung braucht, dann kann man es kompakter schreiben, aber das ist hier nicht der Fall, hier setzt sich das ganze fort.

if (collection is null)
    return;

var enumerator = collection.GetEnumerator ();
enumerator.Reset();

while ( enumerator.MoveNext() )
{
    var value = enumerator.Current;
    ...
}

By the way: Das Reset() braucht man nach dem GetEnumerator()-Aufruf nicht.
Nur wenn man erneut iterieren möchte, braucht man Reset(), was ich persönlich bisher noch nicht hatte.

6.903 Beiträge seit 2009
vor 3 Monaten

Hallo Palladin007,

braucht man Reset(), was ich persönlich bisher noch nicht hatte.

I.d.R. braucht man das auch nicht.
Aber wenns der Iterator gecached werden soll, so lässt sich per Reset der Zustand zurücksetzen bevor in den Cache kommt. Das ist aber schon eher ein Sondefall...(hab das selbst auch erst einmal so verbaut)

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

16.788 Beiträge seit 2008
vor 3 Monaten

Zitat von tomschrot

Das C# Team bei MS ist ziemlich konservativ und neuer Syntax kommt nur in kleinen Dosen.

Weiß nicht woher der Eindruck kommt - aber wir haben jedes Jahr eine neue C# Version mit teilweise sehr viel Änderungen; entweder neuer Syntax oder Syntaxzuckern. Sprache entwickelt sich weiter; altes Zeug wird natürlich nicht entfernt.

https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-12

Zudem ist es nicht "das C# Team bei MS" sondern das ist eine offene C# Design Runde; davon werden teilweise Leute von Microsoft bezahlt.
https://github.com/dotnet/csharplang

Zitat von tomschrot

@Abt: Wenn das alles so doof ist, frage ich mich, warum das dann in C# eingebaut wird?

Die NULL Operatoren sind schon sinnvoll, wenn sie richtig eingesetzt werden.

Oft führen Null Conditional Operators zu einem "cleaneren Code". In Deinem Fall werden sie jedoch inflationär eingesetzt - was genau das Gegenteil bewirkt und genau das Gegenteil der Idee der Einführung von sowas ist. Es ist in der Art sogar einfach nur kontraproduktiv. Salz in der Suppe is toll, nen Kilo auf 200ml jedoch nicht.
Man darf tatsächlich noch normale if-Conditions schreiben.. hier wäre es förderlich.

var enumerator = collection?.GetEnumerator ();
if(enumerator is not null)
{
   enumerator.Reset();

   while ( enumerator.MoveNext() )
   {
      var value = enumerator.Current;
      ...
   }
}

Der Code ist effizienter, lesbarer und entspricht auch mehr Aspekten von Null Conditional Operators.
Sagenhaft, was man alles für ein simples if von den Language Designern so bekommt, wa?

Die wissen halt genau, was sie machen.