Autor Beitrag
Christian S.
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 20451
Erhaltene Danke: 2264

Win 10
C# (VS 2019)
BeitragVerfasst: Fr 25.11.05 17:37 
Hallo!

Ich habe mich heute mit ein paar der neuen Features in C# 2.0 beschäftigt und bin auf die wirklich nette Möglichkeit gestoßen, mittels eines Konstruktes wie diesem hier
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
    public class myList<T>
    {
        private List<T> elements;

        public myList()
        {
            elements = new List<T>();
        }

        public void add(T el)
        {
            elements.Add(el);
        }

        public IEnumerator GetEnumerator()
        {
            foreach (T el in elements)
                yield return el;
        }

    }

einer Klasse einen Enumerator zu verpassen. Gegenüber der "alten" Methode dafür erst noch eine eigene Klasse zu schreiben ein wirklich toller Fortschritt. (Dass der Code oben ansich Quatsch ist, ist mir bewusst, aber ist gut zum testen ;-))

Nun stellt sich mir aber die Frage: Wie funktioniert das genau? Wenn ich nun hergehe und folgenden Code aufrufe:
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
            myList<int> ml = new myList<int>();

            ml.add(3);
            ml.add(4);
            ml.add(5);

            foreach (int i in ml)
                MessageBox.Show(i.ToString());

"Merkt" sich der Compiler dann, dass er beim dritten Element die ersten zwei yield-return-Anweisungen ignorieren muss? :gruebel:

Grüße
Christian

_________________
Zwei Worte werden Dir im Leben viele Türen öffnen - "ziehen" und "drücken".
Robert_G
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 416


Delphi32 (D2005 PE); Chrome/C# (VS2003 E/A, VS2005)
BeitragVerfasst: Fr 25.11.05 23:29 
user profile iconChristian S. hat folgendes geschrieben:
"Merkt" sich der Compiler dann, dass er beim dritten Element die ersten zwei yield-return-Anweisungen ignorieren muss? :gruebel:
Hihi, nö. ... Naja so ähnlich jedenfalls...

Wenn du in einer Methode yield benutzt wird der Compiler eine private nested class anlegen, die selbst IEnumerable[meta]wenn du IEnumerable<T> als rückgabewert hast auch IEnumerable<T>[/meta] implementiert und im Konstruktor die Parameter deiner Methode bekommt.
Jedes yield innerhalb deines Iterators entspricht nachher einem Wert den der ,ebenfalls angelegte, Enumerator zurückgibt.

Ist das Ende deiner Methode erreicht wird MoveNext des Enumerators false liefern.



Auf die Art kann man zum Beispiel direkt durch Dateien iterieren ohne die einzelnen Objekte erst in einen Container werfen zu müssen. :)
Du musst also immer nur ein Objekt im Speicher halten.

Der code deiner Methode wird in die iterator Klasse kopiert und etwas umgebaut. Alle Bezüge auf this/self werden auf ein Feld des iterators umgebogen. Außerdem darfst du leider keine using statements innerhalb des Iterators nehmen... :-/

Hier ein kleiner Bleistift um eine CSV zu durchlaufen:
Pascal:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
class method ConsoleApp.ReadPersonsFrom(reader : StreamReader) : IEnumerable<Person>;  
begin
  while reader.Peek() <> -1 do
  begin
    var splittedLine := reader.ReadLine().Split([';']);

    var person := new Person(splittedLine[0],
                             splittedLine[1],
                             Integer.Parse(splittedLine[2]));
    yield (person);
  end;
end;

class method ConsoleApp.Main;
begin
  using reader := new StreamReader('Persons.txt', Encoding.Default) do
    for person : Person in ReadPersonsFrom(reader) do
      Console.WriteLine('{0}, {1} ({2})',
                        Person.Name,
                        Person.FirstName,
                        Person.Age);
end;


C# port:
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
static IEnumerable<Person> ReadPersonsFrom(StreamReader reader)
{
  while (reader.Peek() != -1)
  {
    string[] splittedLine = reader.ReadLine().Split(new char[1] {';'});

    Person person = new Person(splittedLine[0],
                               splittedLine[1],
                               int.Parse(splittedLine[2]));
    yield return person;
  }
}

static void Main(string[] args)
{
  using (StreamReader reader = new StreamReader("Persons.txt", Encoding.Default))
    foreach (Person person in ReadPersonsFrom(reader))
      Console.WriteLine("{0}, {1} ({2})",
                        person.Name,
                        person.FirstName,
                        person.Age);

}


btw: Iteratoren verwendet man eigentlich nicht in GetEnumerator...
Nein, man kann leider keinen Iterator in einen XmlSerializer werfen...
Christian S. Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 20451
Erhaltene Danke: 2264

Win 10
C# (VS 2019)
BeitragVerfasst: Sa 26.11.05 01:36 
Hm. Ganz verstanden habe ich den Mechanismus noch nicht. Die Methode GetEnumerator aus meinem Beispiel wird also nur einmal aufgerufen, richtig?

Und es werden dann alle Rückgabewerte irgendwo gespeichert und mittels foreach durchlaufen? :gruebel: Wenn das stimmt, was passiert, wenn sich nach Aufruf von GetEnumerator nochmal ein Element ändert?

_________________
Zwei Worte werden Dir im Leben viele Türen öffnen - "ziehen" und "drücken".
Robert_G
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 416


Delphi32 (D2005 PE); Chrome/C# (VS2003 E/A, VS2005)
BeitragVerfasst: Sa 26.11.05 01:49 
user profile iconChristian S. hat folgendes geschrieben:
Hm. Ganz verstanden habe ich den Mechanismus noch nicht. Die Methode GetEnumerator aus meinem Beispiel wird also nur einmal aufgerufen, richtig?
Jupp, zurückgegeben wird die Instanz der Iterator klasse.
Diese wiederum enthält den Code, den du reingeschrieben hast.
Christian S. hat folgendes geschrieben:
Und es werden dann alle Rückgabewerte irgendwo gespeichert und mittels foreach durchlaufen? :gruebel: Wenn das stimmt, was passiert, wenn sich nach Aufruf von GetEnumerator nochmal ein Element ändert?
Nope...
Machen wir es gaaanz simpel:

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
IEnumerable<Integer> CountTil3()
{
  int countDings = 0

  while (countDings < 3)
  {
    countDings++;
    yield return countDings;
  }
}


Die Iterator klasse bekommt nun ein Feld für countDings, welches mit 0 initialisiert wird.
Bei jedem MoveNext wird geprüft, ob countDings < 3 ist. Current liefert den aktuellen Wert von CountDings.
Du hast somit die Möglichkeit durch eine Liste zu iterieren, die gar keine ist. ;)
Christian S. Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 20451
Erhaltene Danke: 2264

Win 10
C# (VS 2019)
BeitragVerfasst: Sa 26.11.05 01:59 
Okay, und die Schleife wird bei jedem "yield" angehalten und beim nächsten Aufruf von MoveNext an genau diesem Punkt fortgesetzt? Sozusagen "eingefroren" und "fortgesetzt". countDings bleibt als Feld des Iterators ja sowieso beim zuletzt zugewiesenen Wert.

_________________
Zwei Worte werden Dir im Leben viele Türen öffnen - "ziehen" und "drücken".
Robert_G
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 416


Delphi32 (D2005 PE); Chrome/C# (VS2003 E/A, VS2005)
BeitragVerfasst: Sa 26.11.05 02:15 
user profile iconChristian S. hat folgendes geschrieben:
Okay, und die Schleife wird bei jedem "yield" angehalten und beim nächsten Aufruf von MoveNext an genau diesem Punkt fortgesetzt? Sozusagen "eingefroren" und "fortgesetzt".
Ich glaube jetzt habe ich dich falsch verstanden...
Deine "Scheife" existiert nicht mehr, die einzige Schleife, die noch existiert ist das foreach, das den Iterator zum Schluss benutzt.

Du hast jetzt folgenden Pseudo code.
  • Konstruktor: this.countDings = 0
  • MoveNext:
    ausblenden C#-Quelltext
    1:
    2:
    3:
    4:
    5:
    6:
    if (countDings < 3)
    {
      countDings++;
      return true;
    }
    return fals;

  • Current: return countDings:
Christian S. Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 20451
Erhaltene Danke: 2264

Win 10
C# (VS 2019)
BeitragVerfasst: Sa 26.11.05 02:21 
Ah, jetzt habe ich es verstanden! :think:
Da muss der Compiler (je nach Komplexität des Codes, der in einen Iterator verwandelt werden soll) allerdings schon einiges leisten!

_________________
Zwei Worte werden Dir im Leben viele Türen öffnen - "ziehen" und "drücken".
Robert_G
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 416


Delphi32 (D2005 PE); Chrome/C# (VS2003 E/A, VS2005)
BeitragVerfasst: Sa 26.11.05 02:59 
Klappt aber prima (siehe Anhang). :)

Gibt's übrigens auch in hübsch:
ausblenden Chrome-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
class method ConsoleApp.CountSomething : IEnumerable<Integer>; 
begin
  for a : Integer := 1 to 3 do
  begin
    yield (a);
    for b : Integer := 10 to 30 step 10 do
      yield (b);
  end;
end;

class method ConsoleApp.Main;
begin
  var values := new LinkedList<Integer>(CountSomething());
end;
Einloggen, um Attachments anzusehen!