Создание экземпляра объекта на основе неограниченного общего типа

У меня есть неограниченный общий тип 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 - это тип класса, поэтому вы сможете использовать конструктор и все другие функции класса без каких-либо дополнительных обходных путей.

Если мое понимание было ошибочным в базовом предположении, я извиняюсь.