Forgive me for digging up the thread, but I had the same problem and solved it in a fairly simple way.
We have a base type that has universal data for all inheriting classes, for example:
type
TThing = class(TObject)
private
FName: String;
public
constructor Create(); virtual; abstract;
public
property Name: String read FName;
end;
and the type of a generic list of such objects:
type
TThings = specialize TFPGObjectList<TThing>;
They inherit two classes from the base class, for example:
type
TSmallThing = class(TThing)
public
constructor Create(); override; // determines the value of the FName field
end;
type
TBigThing = class(TThing)
public
constructor Create(); override; // determines the value of the FName field
end;
Each of them has a dedicated type of generic list, because it is required:
type
TSmallThings = specialize TFPGObjectList<TSmallThing>;
TBigThings = specialize TFPGObjectList<TBigThing>;
Now we need a routine, which has in the parameter to retrieve any list containing objects inheriting from the base class (
TSmallThings or
TBigThings) and which only operates on data from the base class (here: uses the
Name property). If we declare a parameter as a list with objects of the base class:
procedure ShowThingNames(AThings: TThings);
var
Thing: TThing;
begin
for Thing in AThings do
WriteLn(Thing.Name);
WriteLn();
end;
and we will provide a list of the type containing end-class objects, a compilation error related to type mismatches will occur::
var
SmallThings: TSmallThings;
begin
SmallThings := TSmallThings.Create();
ShowThingNames(SmallThings); // Error: Incompatible type
To be able to compile the code, we can use casting (it will work properly), but we will get a warning:
var
SmallThings: TSmallThings;
begin
SmallThings := TSmallThings.Create();
ShowThingNames(TThings(SmallThings)); // Warning: Class types are not related
The simplest solution, which allows to deceive the compiler and not force it to check types (and by the way, to display warnings), is to use the
untyped parameters and
absolute:
procedure ShowThingNames(const AThings);
var
Things: TThings absolute AThings;
Thing: TThing;
begin
for Thing in Things do
WriteLn(Thing.Name);
WriteLn();
end;
Thanks to this, the code will compile without errors and warnings, and we can pass to the routine a list of any objects inheriting from the base class:
var
SmallThings: TSmallThings;
BigThings: TBigThings;
begin
SmallThings := TSmallThings.Create();
BigThings := TBigThings.Create();
try
SmallThings.Add(TSmallThing.Create());
SmallThings.Add(TSmallThing.Create());
BigThings.Add(TBigThing.Create());
BigThings.Add(TBigThing.Create());
ShowThingNames(SmallThings);
ShowThingNames(BigThings);
finally
SmallThings.Free();
BigThings.Free();
end;
end.
The downside of this solution is the lack of control of the data types provided in the parameter — this is not a safe solution. Therefore, you must use such routines with your head, taking care of correctness on your own.
Sources of the test project in the attachment.