11
votes
TMyBaseClass=class
  constructor(test:integer);
end;

TMyClass=class(TMyBaseClass);

TClass1<T: TMyBaseClass,constructor>=class()
  public
    FItem: T;
    procedure Test;
end;

procedure TClass1<T>.Test;
begin
  FItem:= T.Create;
end;

var u: TClass1<TMyClass>;
begin
  u:=TClass1<TMyClass>.Create();
  u.Test;
end;

How do I make it to create the class with the integer param. What is the workaround?

5

5 Answers

16
votes

Just typecast to the correct class:

type
  TMyBaseClassClass = class of TMyBaseClass;

procedure TClass1<T>.Test;
begin
  FItem:= T(TMyBaseClassClass(T).Create(42));
end;

Also it's probably a good idea to make the constructor virtual.

2
votes

You might consider giving the base class an explicit method for initialization instead of using the constructor:

TMyBaseClass = class
public
  procedure Initialize(test : Integer); virtual;
end;  

TMyClass = class(TMyBaseClass)
public
  procedure Initialize(test : Integer); override;
end;

procedure TClass1<T>.Test;
begin
  FItem:= T.Create;
  T.Initialize(42);
end;

Of course this only works, if the base class and all subclasses are under your control.

2
votes

Update

The solution offered by @TOndrej is far superior to what I wrote below, apart from one situation. If you need to take runtime decisions as to what class to create, then the approach below appears to be the optimal solution.


I've refreshed my memory of my own code base which also deals with this exact problem. My conclusion is that what you are attempting to achieve is impossible. I'd be delighted to be proved wrong if anyone wants to rise to the challenge.

My workaround is for the generic class to contain a field FClass which is of type class of TMyBaseClass. Then I can call my virtual constructor with FClass.Create(...). I test that FClass.InheritsFrom(T) in an assertion. It's all depressingly non-generic. As I said, if anyone can prove my belief wrong I will upvote, delete, and rejoice!

In your setting the workaround might look like this:

TMyBaseClass = class
public
  constructor Create(test:integer); virtual;
end;
TMyBaseClassClass = class of TMyBaseClass;

TMyClass = class(TMyBaseClass)
public
  constructor Create(test:integer); override;
end;

TClass1<T: TMyBaseClass> = class
private
  FMemberClass: TMyBaseClassClass;
  FItem: T;
public
  constructor Create(MemberClass: TMyBaseClassClass); overload;
  constructor Create; overload;
  procedure Test;
end;

constructor TClass1<T>.Create(MemberClass: TMyBaseClassClass);
begin
  inherited Create;
  FMemberClass := MemberClass;
  Assert(FMemberClass.InheritsFrom(T));
end;

constructor TClass1<T>.Create;
begin
  Create(TMyBaseClassClass(T));
end;

procedure TClass1<T>.Test;
begin
  FItem:= T(FMemberClass.Create(666));
end;

var 
  u: TClass1<TMyClass>;
begin
  u:=TClass1<TMyClass>.Create(TMyClass);
  u.Test;
end;

Another more elegant solution, if it is possible, is to use a parameterless constructor and pass in the extra information in a virtual method of T, perhaps called Initialize.

0
votes

What seems to work in Delphi XE, is to call T.Create first, and then call the class-specific Create as a method afterwards. This is similar to Rudy Velthuis' (deleted) answer, although I don't introduce an overloaded constructor. This method also seems to work correctly if T is of TControl or classes like that, so you could construct visual controls in this fashion.

I can't test on Delphi 2010.

type
  TMyBaseClass = class
    FTest: Integer;
    constructor Create(test: integer);
  end;

  TMyClass = class(TMyBaseClass);

  TClass1<T: TMyBaseClass, constructor> = class
  public
    FItem: T;
    procedure Test;
  end;

constructor TMyBaseClass.Create(test: integer);
begin
  FTest := Test;
end;

procedure TClass1<T>.Test;
begin
  FItem := T.Create; // Allocation + 'dummy' constructor in TObject
  try
    TMyBaseClass(FItem).Create(42); // Call actual constructor as a method
  except
    // Normally this is done automatically when constructor fails
    FItem.Free;
    raise;
  end;
end;


// Calling:
var
  o: TClass1<TMyClass>;
begin
  o := TClass1<TMyClass>.Create();
  o.Test;
  ShowMessageFmt('%d', [o.FItem.FTest]);
end;
0
votes
type
  TBase = class
    constructor Create (aParam: Integer); virtual;
  end;

  TBaseClass = class of TBase;

  TFabric = class
    class function CreateAsBase (ConcreteClass: TBaseClass; aParam: Integer): TBase;
    class function CreateMyClass<T: TBase>(aParam: Integer): T;
  end;

  TSpecial = class(TBase)
  end;

  TSuperSpecial = class(TSpecial)
    constructor Create(aParam: Integer); override;
  end;

class function TFabric.CreateAsBase(ConcreteClass: TBaseClass; aParam: Integer): TBase;
begin
  Result := ConcreteClass.Create (aParam);
end;

class function TFabric.CreateMyClass<T>(aParam: Integer): T;
begin
  Result := CreateAsBase (T, aParam) as T;
end;

// using
var
  B: TBase;
  S: TSpecial;
  SS: TSuperSpecial;
begin
  B := TFabric.CreateMyClass <TBase> (1);
  S := TFabric.CreateMyClass <TSpecial> (1);
  SS := TFabric.CreateMyClass <TSuperSpecial> (1);