Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generics constructor with parameter constraint?

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?

like image 785
netboy Avatar asked Aug 27 '11 16:08

netboy


3 Answers

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.

like image 79
Ondrej Kelle Avatar answered Sep 29 '22 06:09

Ondrej Kelle


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.

like image 25
jpfollenius Avatar answered Sep 29 '22 06:09

jpfollenius


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.

like image 27
David Heffernan Avatar answered Sep 29 '22 06:09

David Heffernan