I have unconstrained generic type Atomic which implements an initializer (details in my previous question).
type
Atomic<T> = class
type TFactory = reference to function: T;
class function Initialize(var storage: T; factory: TFactory): T;
end;
Now I want to write simplified Initialize function which would take the type information from T (provided that typeof(T) is tkClass) and create new instance (when necessary) with the default constructor.
Sadly, this fails:
class function Atomic<T>.Initialize(var storage: T): T;
begin
if not assigned(PPointer(@storage)^) then begin
if PTypeInfo(TypeInfo(T))^.Kind <> tkClass then
raise Exception.Create('Atomic<T>.Initialize: Unsupported type');
Result := Atomic<T>.Initialize(storage,
function: T
begin
Result := TClass(T).Create; // <-- E2571
end);
end;
end;
Compiler reports error E2571 Type parameter 'T' doesn't have class or interface constraint
.
How can I trick the compiler to create an instance of class T?
You can use GetTypeData
to obtain the class reference:
Result := T(GetTypeData(PTypeInfo(TypeInfo(T)))^.ClassType.Create);
In Delphi XE2 (and hopefully in next releases), you can do:
var
xInValue, xOutValue: TValue;
xInValue := GetTypeData(PTypeInfo(TypeInfo(T)))^.ClassType.Create;
xInValue.TryCast(TypeInfo(T), xOutValue);
Result := xOutValue.AsType<T>;
(This rather circumvent way was discovered by used cjsalamon
in the OmniThreadLibrary forum: Error in OtlSync XE2.)
You can use the new Delphi Rtti
to make this task. The drawback of given solution is that it won't work if the constructor isn't named as Create. If you need to make it work all the time, just enumerate your type methods, check if it's a constructor and have 0 parameters and then invoke it. Works in Delphi XE. Sample code:
class function TTest.CreateInstance<T>: T;
var
AValue: TValue;
ctx: TRttiContext;
rType: TRttiType;
AMethCreate: TRttiMethod;
instanceType: TRttiInstanceType;
begin
ctx := TRttiContext.Create;
rType := ctx.GetType(TypeInfo(T));
AMethCreate := rType.GetMethod('Create');
if Assigned(AMethCreate) and rType.IsInstance then
begin
instanceType := rType.AsInstance;
AValue := AMethCreate.Invoke(instanceType.MetaclassType, []);// create parameters
Result := AValue.AsType<T>;
end;
end;
Updated solution:
class function TTest.CreateInstance<T>: T;
var
AValue: TValue;
ctx: TRttiContext;
rType: TRttiType;
AMethCreate: TRttiMethod;
instanceType: TRttiInstanceType;
begin
ctx := TRttiContext.Create;
rType := ctx.GetType(TypeInfo(T));
for AMethCreate in rType.GetMethods do
begin
if (AMethCreate.IsConstructor) and (Length(AMethCreate.GetParameters) = 0) then
begin
instanceType := rType.AsInstance;
AValue := AMethCreate.Invoke(instanceType.MetaclassType, []);
Result := AValue.AsType<T>;
Exit;
end;
end;
end;
And call it like this:
var
obj: TTestObj;
begin
obj := TTest.CreateType<TTestObj>;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With