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.
1 comment:
That is some truely ugly code heading deep into unmaintainable territory.
Can you imagine coming back in 18 months or more and trying to figure out what it does? (or someone else for that matter)
Post a Comment