Dass man seit einigen Delphi Versionen mit der for-in-Schleife elegant über Listen, Enums und Arrays rutschen kann, wissen ja inzwischen viele.
Wahrscheinlich wissen auch einige, was man selber implementieren muss, um ein
eigenes Objekt enumerable zu machen.
Aber habt ihr euch schonmal gewünscht ein Enumerable zur Laufzeit selber zu erstellen, ohne es erst in einen enumerable Container (z.B. Array oder Liste) zu werfen?
Angenommen, ich möchte die Fibonacci Zahlen ermitteln und ausgeben.
Zugegeben, das Beispiel ist eventuell ein bisschen simpel, aber ich finde, man kann damit gut das Prinzip darstellen.
Ich benutz mal die iterative Lösung (jaja, da kann man bestimmt noch was optimieren, ich weiß):
Delphi-Quellcode:
procedure FibonacciNumbers;
var
a, b, c: UInt64;
begin
a := 0;
b := 1;
Writeln(a);
Writeln(b);
repeat
c := a + b;
a := b;
b := c;
Writeln(c);
until c > (High(UInt64) div 2);
end;
Ok, fein, das gibt mir also alle Fibonacci Zahlen aus, die mit UInt64 möglich sind.
Was ist, wenn ich die Zahlen nun in ne Liste packen will? Ich schreib statt Writeln dort nen list.Add rein. Was wäre, wenn ich folgendes machen könnte:
Delphi-Quellcode:
var
i: Uint64;
begin
for i in FibonacciNumbers() do
Writeln(i); // oder list.Add(i);
end;
Ich kann auch die Anzahl der ermittelten Zahlen bekommen:
FibonacciNumbers().Count();
Oder ich will aus irgendeinem Grund die ersten 20 überspringen und dann nur die nächsten 50 sehen:
for i in FibonacciNumbers().Skip(20).Take(50) do
Schauen wir uns die Funktion mal an, die wir dazu bauen müssen:
Delphi-Quellcode:
function FibonacciNumbers: IEnumerable<UInt64>;
begin
Result := TEnumerable<UInt64>.Create(
procedure
var
a, b, c: UInt64;
begin
a := 0;
b := 1;
Yield(a);
Yield(b);
repeat
c := a + b;
a := b;
b := c;
Yield(c);
until c > High(UInt64) div 2;
end);
end;
Als erstes fällt auf, dass die Funktion ein Interface vom Typ IEnumerable<UInt64> zurück gibt. Aha, deshalb kann man also mit der for-in Schleife darüber laufen und sogar weitere Funktionen aufrufen?!
Genau, und das Objekt, was dahinter steckt erstellen wir in der Funktion und übergeben ihm eine anonyme Methode, welche die Daten bereit stellt.
Aber was ist dieses Yield?? Damit kann man quasi jedesmal den Programmablauf innerhalb dieser anonymen Methode wieder abgeben. Dieser kehrt beim nächsten Zugriff auf .Next des Enumerators wieder an diese Stelle zurück. Dies wird intern über ein Fiber gemacht. Dabei habe ich mich an
dieser Implementierung orientiert und das ganze noch erweitert.
Außerdem habe ich einige Helper Klassen erstellt um das einigen von .Net bekannte Verhalten der Enumerators dort nachzuempfinden.
Um das ganze zu benutzen muss die Generics.Enumerators ins Uses gepackt werden, die anderen Units müssen nur durch Suchpfad erreichbar sein.
Das ganze ist wie immer nicht im Großen und Ganzen durchgetestet, aber ich habe damit immerhin schon ein kleines Framework im Linq-To-
SQL Stil realisiert.