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:
for
xxxin
TFilteredEnumerator<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
...
for
curin
TFilteredEnumerator<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.