Sometimes combining different programming techniques reveals the hidden beauty of a programming language.
A few years ago I was able to create such a thing with some older and newer language features. The outcome revealed some kind of magic that really amazed myself.
The recipe is pretty simple: take an enumerator, mix it with a class helper and pour it over an invokeable custom variant.
A what?
Forget it for the moment and let’s start at the beginning.
The idea was to iterate over a dataset in an easy way avoiding that tedious writing like:
1 2 3 4 5 |
dataset.First; while not dataset.EOF do begin // do something with the current record dataset.Next; end; |
I had something more elegant in mind, something like:
1 2 3 |
for data in dataset do begin // do something with data end; |
As TDataSet lacks an enumerator I decided to write one by myself. No problem with that one, only a few things to implement. My first approach looked like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
type TDataSetEnumerator = class private FDataSet: TDataSet; FCounter: Integer; function GetCurrent: Integer; public constructor Create(ADataSet: TDataSet); destructor Destroy; override; function MoveNext: Boolean; property Current: Integer read GetCurrent; end; constructor TDataSetEnumerator.Create(ADataSet: TDataSet); begin inherited Create; FDataSet := ADataSet; FDataSet.Open; FCounter := -1; // to indicate we want the first record end; destructor TDataSetEnumerator.Destroy; begin FDataSet.Close; end; function TDataSetEnumerator.GetCurrent: Integer; begin Result := FCounter; end; function TDataSetEnumerator.MoveNext: Boolean; begin if FCounter < 0 then begin FDataSet.First; FCounter := 0; end else begin FDataSet.Next; Inc(FCounter); end; Result := not FDataSet.EoF; end; |
At that time I found it a brilliant idea to have the current record index as the iteration variable.
Now how to squeeze that enumerator into the dataset? Class helpers came to the rescue:
1 2 3 4 5 6 7 8 9 10 |
type TDataSetHelper = class helper for TDataSet public function GetEnumerator: TDataSetEnumerator; end; function TDataSetHelper.GetEnumerator: TDataSetEnumerator; begin Result := TDataSetEnumerator.Create(Self); end; |
With that I was able to write my dataset loop as:
1 2 3 4 5 6 7 |
var recIndex: Integer; for recIndex in dataset do begin // do something with the the current record // Hey, and BTW, you can find the current record index in recIndex! end; |
So the first step was done by combining an enumerator with a class helper.
And what have we gained now? We reduced the original code by just two lines for the cost of adding a new variable yielding an overall of one line profit (if we don’t count for the var that probably has been there before). We have to write quite a couple of loops to outweigh the enumerator and class helper code, do we?
What bothered me most was that the body of the loop remained unchanged. All that referencing of the record fields with
1 2 3 |
DataSet.FieldValues['First_Name'] // or if you prefer this DataSet.FieldByName('First_Name').Value |
was still cluttered all over the code. I felt like I just missed the point.
I just had not reached my target and the longer part of the way still laid ahead. But more on that in the second part…
A very nice (almost) first post! 😀 The second one is also pretty cool.