I ended up with code used like this:
for item in Flist.FilteredEnumerator(function (TestObject: TtiOPFTestIntegerProp): Boolean
begin
result:= TestObject.IntField mod 2 = 1;
end) do
begin
inc(intCount);
intSum:= intSum + item.IntField;
end;
I was happy enough with the result, but I wasn't happy with the implementation. I wanted something that was, well, more generic. What I ended up doing was wrapping the existing enumerator into one containing a filter. This will work with any generic collection that descends from TEnumerable
To implement an enumerator, a class must have a GetEnumerator function. This returns an object (or a record) that has the Current property and the MoveNext function. Delphi does a fair amount of work behind the scenes to wrap this all up nicely. See The Delphi Geek's series on enumerators here for more background.
Wrapping an existing enumerator meant I could use the existing GetCurrent and MoveNext for accessing the collection. Filtering then becomes as simple as:
function TFilteredEnumerator<T>.MoveNext: Boolean;
begin
while FEnumerator.MoveNext do
begin
if FPredicate(FEnumerator.Current) then
exit(true);
end;
result:= false;
end;The full code is as follows:unit FilteredEnumeratorU;
interface
uses Sysutils, generics.collections;
type
TFilteredEnumerator = class
private
FEnumerator: TEnumerator;
FPredicate: TPredicate;
protected
function DoGetCurrent: T;
public
constructor Create(AEnumerable: TEnumerable; APredicate: TPredicate);
destructor Destroy;
function GetEnumerator: TFilteredEnumerator;
property Current: T read DoGetCurrent;
function MoveNext: Boolean;
end;
implementation
{ TFilteredEnumerator }
constructor TFilteredEnumerator.Create(AEnumerable: TEnumerable; APredicate: TPredicate);
begin
inherited create;
FEnumerator:= AEnumerable.GetEnumerator;
FPredicate:= APredicate;
end;
destructor TFilteredEnumerator.Destroy;
begin
FEnumerator.Free;
inherited Destroy;
end;
function TFilteredEnumerator.DoGetCurrent: T;
begin
result:= FEnumerator.Current;
end;
function TFilteredEnumerator.GetEnumerator: TFilteredEnumerator;
begin
result:= self;
end;
function TFilteredEnumerator.MoveNext: Boolean;
begin
while FEnumerator.MoveNext do
begin
if FPredicate(FEnumerator.Current) then
exit(true);
end;
result:= false;
end;
end.
To use filtering in action, simply do something like:
forxxxinTFilteredEnumerator<T>.Create(queue,function(Arg1: T): Boolean
begin
result:= ...;
end)do
...
eg
var
queue: TQueue<string>;
cur, combined:string;
filter: TFilteredEnumerator<TTestObject>;begin
queue:= TQueue<string>.Create;try
...
forcurinTFilteredEnumerator<string>.Create(queue,function(Arg1:string): Boolean
begin
result:= Arg1 <'A';
end)
do
begin
combined:= combined + cur;
end;
If you are deriving from a collection, you could also wrap this into a method:
function TFilterableList.Filter(APredicate: TPredicate<ttestobject>): TFilteredEnumerator<ttestobject>;
begin
result:= TFilteredEnumerator<ttestobject>.Create(self, APredicate);
end;
Source can be downloaded from here.