In the last post I have described how enumeration works in Delphi. Now I will try to expand the subject a bit and make a more general description of what enumerability actually means and how it can solve some basic problems and patterns.
“An enumrebale is not necessarily a collection“. You should keep in mind that enumerability doesn’t apply only to collections. It is a more abstract concept. Enumeration can apply to any kind of sequence — abstract, mathematical or a collection. As an example let’s talk about streams — streams are also enumerables, in some cases these behave exactly like enumerable collections: For streams that do not have a defined size (like for example downloading a file over a HTTP stream that doesn’t say the file size). All you can do in these cases is to read bytes from the stream until you read the bottom and receive an EOF notification.
“Abstract vs concrete sequences“. An abstract sequence in my definition is one that doesn’t occupy space and each new element is generated on the fly by the enumerable. A concrete sequence is more akin to a collection which already has its elements stored somewhere and all it does is to fetch them when enumerating.
In the next example I took a known mathematical sequence which I am sure all of you are acquainted with: the Fibonacci sequence; and created an abstract enumerable which at each iteration calculates the next number:
type TFibonacciEnumerator = record private FCurrent, FPrev, FCount, FMaxCount: Cardinal; function GetCurrent: Cardinal; public { Move to the next calculation } function MoveNext(): Boolean; { Reads the current number } property Current: Cardinal read GetCurrent; end; Fibonacci = record private FLimit: Cardinal; public { Returns the enumerator object } function GetEnumerator(): TFibonacciEnumerator; { Static function that } class function OfLength(const ALength: Cardinal): Fibonacci; static; end; { TFibonacciEnumerator } function TFibonacciEnumerator.GetCurrent: Cardinal; begin Result := FCurrent; end; function TFibonacciEnumerator.MoveNext: Boolean; var LTemp: Cardinal; begin { Check if the end of the chain } if FCount >= FMaxCount then Exit(false); Result := true; { Make the next calculation } if FCount <= 1 then FCurrent := FCount else begin LTemp := FCurrent; FCurrent := FCurrent + FPrev; FPrev := LTemp; end; Inc(FCount); end; { Fibonacci } function Fibonacci.GetEnumerator: TFibonacciEnumerator; begin Result.FCurrent := 0; Result.FPrev := 0; Result.FCount := 0; Result.FMaxCount := FLimit; end; class function Fibonacci.OfLength(const ALength: Cardinal): Fibonacci; begin if ALength = 0 then raise EArgumentOutOfRangeException.Create('ALength should be > 0'); Result.FLimit := ALength; end; var Number: Cardinal; begin { Show the first 100 Fibonacci numbers } for Number in Fibonacci.OfLength(100) do WriteLn(Number); ReadLn; end.
What is cool about this example is the fact that you do not occupy any memory with the calculated numbers, those are made on-the-fly.
This example did not demonstrate another important aspect of enumerables and exactly: “materializing” abstract sequences. If my Fibonacci record was actually a class derived from TEnumerable<Cardinal>, I could have written this:
var List: TList<Cardinal>; begin List := TList<Cardinal>.Create(Fibonnaci.OfLength(100)); end.
This would have “materialized” the abstract sequence generated by the Fibonacci object and stored each value in a concrete sequence (the List collection).
Unfortunately Delphi’s generics support is at it’s infancy so not many features are yet available in the standard classes to exploit the power of Enumerables. I predict this will change over time and more cool stuff will appear either directly in the RTL or in form of 3rd-party libraries.