Fun with enumerators, part 3 - parameterized enumerators
[Part 1 - Introduction, Part 2 - Additional enumerators.] In first two installments of this series, we took a look at for..in statement, described Delphi support for custom enumerators and demonstrated one possible way to add additional enumerator to a class that already implements one. Today we'll implement a parameterized enumerator. Enumerators of this kind can be especially handy when used as filters. For example, an enumerator that takes a class reference as a parameter can be used to filter a TObjectList containing objects of many different classes. IOW, we would like to write a following construct: for obj in list.Enum(TSomeClass) do. Clearly, Enum cannot be a property anymore, but it can be a function. We can still use an enumerator factory object stored inside our class (as in the Part 2). Besides that, we must implement enumeration function that will take one or more parameters, preserve them inside the factory object and return the factory object so that Delphi compiler can call its GetEnumerator function. Sounds complicated but in reality it really is simple. First we have to add an internal factory object and an enumeration function to the TCustomStringList class. TCustomStringList = class(TStringList) Enumeration function SkipEveryNth takes one parameter and passes it to the factory object. Then it returns this factory object. We also need a new factory class. This is an extended version of factory class from Part 2. It must implement storage for all enumerator parameters. In this case, this storage is implemented via property Skip. TCSLEnumEveryNthFactory = class As you can see, GetEnumerator was also changed - in addition to passing FOwner to the created enumerator, it also passes current value of the Skip property. New enumerator is very similar to the one from the Part 2, except that it advances list index by the specified ammount (instead of advancing it by 2). TCSLEnumEveryNth = class We can now write some test code. procedure TfrmFunWithEnumerators.btnSkipEveryThirdClick(Sender: TObject); Delphi compiler will translate this for..in roughly into enumerator := FslTest.SkipEveryNth(3).GetEnumerator; So what is going on here?
Test code result: Tomorrow we'll do something even more interesting - we'll add new enumerator to existing class without creating a derived class. Labels: Delphi, enumerators, programming, source code |
8 Comments:
This is interesting stuff, and don't get me wrong, I'll certainly be looking out for the next installment. But... you need more lines to hook into the compiler's for/in syntax than if you didn't bother! Cf.:
ICSLEnum = interface
function FindNext(out S: string): Boolean;
end;
TCStringList = class(TStringList)
public
function CreateEnumerator(Skip: Integer): ICSLEnum;
end;
TCSLEnum = class(TInterfacedObject, ICSLEnum)
private
FListIndex: Integer;
FOwner: TCStringList;
FSkip: Integer;
protected
function FindNext(out S: string): Boolean;
public
constructor Create(Owner: TCStringList;
Skip: Integer);
end;
constructor TCSLEnum.Create(Owner: TCStringList; skip: integer);
begin
FOwner := Owner;
FSkip := Skip;
end;
function TCSLEnum.FindNext(out S: string): Boolean;
begin
Result := (FListIndex < FOwner.Count);
if not Result then Exit;
S := FOwner[FListIndex];
Inc(FListIndex, FSkip);
end;
function TCStringList.CreateEnumerator(Skip: Integer): ICSLEnumEveryNth;
begin
Result := TCSLEnum.Create(Self, Skip);
end;
with FslTest.CreateEnumerator(3) do
while FindNext(s) do ln := ln + s;
I've shortened the class names so it looks better in the comment box. Anyhow, note there's no factory class needed, which keeps the overall line count down. Also, what replaces the actual for/in construct takes the same number of lines (i.e., 2) that your original did.
Sure, but that way you cannot use the for..in :)
I've always been an advocate of the school that says "write more code when preparing an infrastructure so you can write less code when using this infrastructure"
Very nice stuff, one question I have is will this work with BDS 2007, 8.. or is this an undocumented feature?
thanks again
Dave N.
This is all fully documented (I even quoted Delphi help in Part 1) and will continue to work in future Delphis.
I've always been an advocate of the school that says "write more code when preparing an infrastructure so you can write less code when using this infrastructure"
Yeah, but in my version, the 'infrastructure' amounts to less code and the 'use' of it amounts to about the same... Anyhow, I'll be looking out now for part 5. You're going to be creating a class helper, I assume...
Of course :)
With plenty of dire warnings.
This is a nice example, except that it isn't thread-safe. In fact, worse than that, it isn't even possible to have two enumerators (with different Skip factors) on the same string list at one time, even on a single thread (a for in loop nested within another for in loop).
To run multiple enumerators on one structure, you have to use enumerator factory and interfaces. See parts 4, 5, and 6 for more information.
As for the thread-safety - threading is hard. My enumerator code doesn't try at all to be multithread-aware.
Post a Comment
Links to this post:
Create a Link
<< Home