I can't figure out how to loop a generic TList instance, do I need to use TValue or the ReinterpretCast trick?
I can't figure out how to loop a generic TList instance, do I need to use TValue or the ReinterpretCast trick?
procedure Foo( AInstance : TObject);
begin
// how to do here a for loop ?
if AInstance is a TList then... ??
...
end;
var MyList : TList;
...
Foo(MyList);
procedure Foo( AInstance : TObject);
begin
// how to do here a for loop ?
if AInstance is a TList
...
end;
var MyList : TList
...
Foo(MyList);
why can't Foo be a method of the list?
ReplyDeleteMyList.Foo;
I.e. where you use TList, you rather use TMyList where TMyList have the Foo method.
ReplyDeleteUsing if/then class comparisons is kinda non-OOP.
ReplyDeleteprocedure Foo(AInstance: TObject);
ReplyDeletevar
LList: TList;
LSomething: TSomething;
begin
if AInstance is TList then
begin
LList := TList(AInstance);
for LSomething in LList do
begin
//
end;
end;
end;
Or why have AInstance to be a TObject? This is a lot shorter and easier ;-)
procedure Foo(AInstance: TList;);
var
LSomething: TSomething;
begin
for LSomething in LList do
begin
//
end;
end;
Just enumerate the list after casting. This compiles and works (had a free minute during a download):
ReplyDeletetype
TSomething = class
end;
var
MyList : TList;
procedure Foo( AInstance : TObject);
var
sm: tSomething;
begin
// how to do here a for loop ?
if AInstance is TList then
begin
for sm in TList (AInstance) do
ShowMessage (sm.ClassName)
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
MyList := TList.Create;
MyList.Add(TSomething.Create);
Foo(MyList);
end;
Bye
The problem is TSomething is declared in a unit that is not accessible from Foo. For non-generic TList its just a "is" and cast TList(Ainstance), but for generic TList the casting works but the "is" does not.
ReplyDeleteAlso Foo is called with different TList types, not only TSomething. (ie: parsing streamed properties that can be of any type)
ReplyDeleteSo you want to enumerate a TListOrTListDescendent?
ReplyDeleteDoes these TUnknown have a common ancestor that you will be accessing methods or properties from?
Yes, its a TObject, but "if AInstance is TList" returns false. Casting TList(AInstance) is ok, for any generic TList or non-generic classic TList. I'll try with rtti, maybe there is a way to solve the "is", or Typeinfo comparison.
ReplyDeleteDavid Berneda If TSomething is not accessable in Foo then you cannot write if AInstance is TList. To me it sounds as if you need something like covariance here (which is not possible in Delphi).
ReplyDeleteIf you know for sure that the T in TList is a class then you can write this:
procedure Foo(AList: TList)
var
obj: TObject
begin
for obj in AList do
..
end;
Foo(TList(MyList)); // hardcast knowing that MyList is a TList where T is a class, otherwise this will possibly crash at runtime.
For more complex scenarios I suggest taking a look at the Spring4D collections as they provide more flexible ways of handling different types of T (i.e. possibility of getting the type of T at runtime or "cast" them to a non generic list type).
Found a quite satisfaying solution using Rtti and TValue, looking for the public "List" property of generic arrays.
ReplyDeleteThanks to everybody !!
uses Rtti;
procedure Foo(AInstance: TObject);
var
tmpValue : TValue;
tmpList : TRttiProperty;
tmpContext : TRttiContext;
tmpType : TRttiType;
t : Integer;
begin
tmpContext:=TRttiContext.Create;
try
tmpType:=tmpContext.GetType( AInstance.ClassInfo );
tmpList:=tmpType.GetProperty('List');
if tmpList.PropertyType is TRttiDynamicArrayType then
begin
tmpValue:=tmpList.GetValue(AInstance);
for t:=0 to tmpValue.GetArrayLength do
DoSomething( tmpValue.GetArrayElement(t).AsObject );
end;
end;
David Berneda Just be aware of the performance hit when using RTTI like that, it's quite slow indeed.
ReplyDeleteAsbjørn Heid No problem ! Its done when loading a property from a form so its not speed critical.
ReplyDelete