Thinking about a generic class
type
TMyClass<T> = class
end;
If we would like to dynamically generate new instances of the type T (and possibly return it), what would you think of?
The official way
There is a built-in way to do it by declaring T as both type class and a constructor:
type
TMyClass<T: class, constructor> = class
end;
By doing this way, we will be able to initiate a new instance by simply calling T.Create
without knowing what class the T is. But wait, there are classes that must be constructed with its own create method instead of the default TObject.Create
, like TForm
, TThread
and any classes with custom parametered constructor.
Failed attempts
First, I came up with a new class like this:
type
TMyClass<T: TForm> = class
end;
And attempted to instantiate with My := TMyClass<TForm1>
.
But the compiler didn't allow me to call T.Create(nil)
because the compiler doesn't understand that the TForm
is a class and has a Create
method.
Then I tried to do a typecast like this:
My := TForm1(T).Create;
However, the compiler complained that TForm1 is incompatible with TForm
. WTF?!
Later, I realized that this is to prevent from accidentally return an ancestor class as a child class, e.g.
TMyClass<T: TForm> = class
function init(): T;
end;
function TMyClass<T>.init(): T;
var
subclass: T;
begin
subclass:= T.Create(nil); // <-- error: Create is undefined
subclass:= TForm(T).Create(nil); // <-- error: TForm1 is not compatible with TForm
end;
var
v1: TMyClass<TForm1>;
The second subclass creator actually created a TForm base class and returned it as its super class TForm1 which is certainly not allowed.
The RTTI way
With RTTI, we are able to locate the Create method, and invoke it with any parameters we want
function TMyClass<T>.NewInstance(): T;
var
p: TRttiParameter;
ctx: TRttiContext;
rtype: TRttiType;
method: TRttiMethod;
params: TArray<TRttiParameter>;
I: Integer;
realParams: TArray<TValue>;
begin
rtype:= ctx.GetType(TypeInfo(T));
method:= rtype.GetMethod('Create'); // Find the Create method
params := method.GetParameters; // get parameters info
SetLength(realParams, Length(params));
for I := 0 to Length(params) - 1 do
case params[I].ParamType.TypeKind of // do whatever you need to initialize parameters
tkClass:
realParams[I] := TValue.From<TObject>(nil);
tkString:
realParams[I] := TValue.From<string>('');
tkUString:
realParams[I] := TValue.From<UnicodeString>('');
tkInteger, tkInt64:
realParams[I] := TValue.From<Integer>(0);
end;
result := method.Invoke(rtype.AsInstance.MetaclassType, params ).AsType<T>; // call the constructor
end;
Voila!
References:
0 comment