Создание экземпляра объекта на основе неограниченного общего типа
У меня есть неограниченный общий тип Atomic, который реализует инициализатор (подробности в предыдущем вопросе ).
type
Atomic<T> = class
type TFactory = reference to function: T;
class function Initialize(var storage: T; factory: TFactory): T;
end;
Теперь я хочу написать упрощенную функцию Initialize, которая будет принимать информацию о типе из T (при условии, что typeof (T) является tkClass) и создать новый экземпляр (при необходимости) с помощью конструктора по умолчанию.
К сожалению, это не удается:
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;
Компилятор сообщает об ошибке E2571 Type parameter 'T' doesn't have class or interface constraint
.
Как я могу обмануть компилятор для создания экземпляра класса T?
Ответы
Ответ 1
Вы можете использовать новый Delphi Rtti
для выполнения этой задачи. Недостатком данного решения является то, что он не будет работать, если конструктор не называется Create. Если вам нужно заставить его работать все время, просто перечислите методы типа, проверьте, является ли он конструктором и имеют 0 параметров, а затем вызывают его. Работает в Delphi XE. Пример кода:
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;
Обновленное решение:
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;
И назовите его следующим образом:
var
obj: TTestObj;
begin
obj := TTest.CreateType<TTestObj>;
Ответ 2
Вы можете использовать GetTypeData
, чтобы получить ссылку на класс:
Result := T(GetTypeData(PTypeInfo(TypeInfo(T)))^.ClassType.Create);
В Delphi XE2 (и, надеюсь, в следующих выпусках) вы можете:
var
xInValue, xOutValue: TValue;
xInValue := GetTypeData(PTypeInfo(TypeInfo(T)))^.ClassType.Create;
xInValue.TryCast(TypeInfo(T), xOutValue);
Result := xOutValue.AsType<T>;
(Этот довольно обходный путь был обнаружен с помощью cjsalamon
на форуме OmniThreadLibrary: Ошибка в OtlSync XE2.)
Ответ 3
Если я правильно понял, общий тип "T
" - это класс. В этом случае просто объявите:
Atomic< T: class > = class
вместо плоской
Atomic< T > = class
Это сообщит компилятору, что T
- это тип класса, поэтому вы сможете использовать конструктор и все другие функции класса без каких-либо дополнительных обходных путей.
Если мое понимание было ошибочным в базовом предположении, я извиняюсь.