Delphi: Using Enumerators to filter TList<T: class> by class type?
Okay, this might be confusing. What I'm trying to do is use an enumerator to only return certain items in a generic list based on class type.
Given the following hierarchy:
type
TShapeClass = class of TShape;
TShape = class(TObject)
private
FId: Integer;
public
function ToString: string; override;
property Id: Integer read FId write FId;
end;
TCircle = class(TShape)
private
FDiameter: Integer;
public
property Diameter: Integer read FDiameter write FDiameter;
end;
TSquare = class(TShape)
private
FSideLength: Integer;
public
property SideLength: Integer read FSideLength write FSideLength;
end;
TShapeList = class(TObjectList<TShape>)
end;
How can I extend TShapeList
such that I can do something similar to the following:
procedure Foo;
var
ShapeList: TShapeList;
Shape: TShape;
Circle: TCircle;
Square: TSquare;
begin
// Create ShapeList and fill with TCircles and TSquares
for Circle in ShapeList<TCircle> do begin
// do something with each TCircle in ShapeList
end;
for Square in ShapeList<TSquare> do begin
// do something with each TSquare in ShapeList
end;
for Shape in ShapeList<TShape> do begin
// do something with every object in TShapeList
end;
end;
I've tried extending TShapeList
using an adapted version of Primoz Gabrijelcic's bit on Parameterized Enumerators using a factory record as follows:
type
TShapeList = class(TObjectList<TShape>)
public
type
TShapeFilterEnumerator<T: TShape> = record
private
FShapeList: TShapeList;
FClass: TShapeClass;
FIndex: Integer;
function GetCurrent: T;
public
constructor Create(ShapeList: TShapeList);
function MoveNext: Boolean;
property Current: T read GetCurrent;
end;
TShapeFilterFactory<T: TShape> = record
private
FShapeList: TShapeList;
public
constructor Create(ShapeList: TShapeList);
function GetEnumerator: TShapeFilterEnumerator<T>;
end;
function FilteredEnumerator<T: TShape>: TShapeFilterFactory<T>;
end;
Then I modified Foo
to be:
procedure Foo;
var
ShapeList: TShapeList;
Shape: TShape;
Circle: TCircle;
Square: TSquare;
begin
// Create ShapeList and fill with TCircles and TSquares
for Circle in ShapeList.FilteredEnumerator<TCircle> do begin
// do something with each TCircle in ShapeList
end;
for Square in ShapeList.FilteredEnumerator<TSquare> do begin
// do something with each TSquare in ShapeList
end;
for Shape in ShapeList.FilteredEnumerator<TShape> do begin
// do something with every object in TShapeList
end;
end;
However, Delphi 2010 is throwing an error when I try to compile Foo
about Incompatible types: TCircle and TShape
. If I comment out the TCircle
loop, then 开发者_如何转开发I get a similar error about TSquare
. If I comment the TSquare
loop out as well, the code compiles and works. Well, it works in the sense that it enumerates every object since they all descend from TShape
. The strange thing is that the line number that the compiler indicates is 2 lines beyond the end of my file. In my demo project, it indicated line 177, but there's only 175 lines.
Is there any way to make this work? I'd like to be able to assign to Circle directly without going through any typecasts or checking in my for
loop itself.
You don't show it here, but the problem probably lies in the implementation of GetCurrent.
While the compiler accepts something like
result := FShapeList[FIndex];
in the generic class, this will fail when the result class doesn't equal TShape, which is the case for TCircle and TSquare. That's why it works in the third loop.
Change the code to
result := T(FShapeList[FIndex]);
and you are fine.
The non-existing line number for the error is due to resolving the internal generic done by the compiler which doesn't map well to line numbers.
You can't do that directly in the enumerator. I'm afraid you're stuck with "is". The only way is indeed to create some helper enumerator. In your case try commenting out lines until you find the only that's making the compiler unhappy.
Sorry that's quite all I can suggest.
You wrote the answer in your question :)
You just have to call the right function :
for Circle in ShapeList.FilteredEnumerator<TCircle> do
//blah blah
for Square in ShapeList.FilteredEnumerator<TSquare> do
//blah blah
One small remark about your code : you can dump your TShapeFilterFactory
record, and simply add a method :
TShapeFilterEnumerator<T>.GetEnumerator : TShapeFilterEnumerator<T>;
begin
Result := Self;
end;
精彩评论