How can I create an Delphi object from a class reference and ensure constructor execution?
Solution 1
Use this:
type
TMyClass = class(TObject)
MyStrings: TStrings;
constructor Create; virtual;
end;
TMyClassClass = class of TMyClass; // <- add this definition
constructor TMyClass.Create;
begin
MyStrings := TStringList.Create;
end;
procedure Test;
var
Clazz: TMyClassClass; // <- change TClass to TMyClassClass
Instance: TObject;
begin
Clazz := TMyClass; // <- you can use TMyClass or any of its child classes.
Instance := Clazz.Create; // <- virtual constructor will be used
end;
Alternatively, you can use a type-casts to TMyClass (instead of "class of TMyClass").
Solution 2
Alexander's solution is a fine one but does not suffice in certain situations. Suppose you wish to set up a TClassFactory class where TClass references can be stored during runtime and an arbitrary number of instances retrieved later on.
Such a class factory would never know anything about the actual types of the classes it holds and thus cannot cast them into their according meta classes. To invoke the correct constructors in such cases, the following approach will work.
First, we need a simple demo class (don't mind the public fields, it's just for demonstration purposes).
interface
uses
RTTI;
type
THuman = class(TObject)
public
Name: string;
Age: Integer;
constructor Create(); virtual;
end;
implementation
constructor THuman.Create();
begin
Name:= 'John Doe';
Age:= -1;
end;
Now we instantiate an object of type THuman purely by RTTI and with the correct constructor call.
procedure CreateInstance();
var
someclass: TClass;
c: TRttiContext;
t: TRttiType;
v: TValue;
human1, human2, human3: THuman;
begin
someclass:= THuman;
// Invoke RTTI
c:= TRttiContext.Create;
t:= c.GetType(someclass);
// Variant 1a - instantiates a THuman object but calls constructor of TObject
human1:= t.AsInstance.MetaclassType.Create;
// Variant 1b - same result as 1a
human2:= THuman(someclass.Create);
// Variant 2 - works fine
v:= t.GetMethod('Create').Invoke(t.AsInstance.MetaclassType,[]);
human3:= THuman(v.AsObject);
// free RttiContext record (see text below) and the rest
c.Free;
human1.Destroy;
human2.Destroy;
human3.Destroy;
end;
You will find that the objects "human1" and "human2" have been initialized to zero, i.e., Name='' and Age=0, which is not what we want. The object human3 instead holds the default values provided in the constructor of THuman.
Note, however, that this method requires your classes to have constructor methods with not parameters. All the above was not conceived by me but explained brillantly and in much more detail (e.g., the c.Free part) in Rob Love's Tech Corner.
Solution 3
Please check if overriding AfterConstruction is an option.
Solution 4
Your code slightly modified:
type
TMyObject = class(TObject)
MyStrings: TStrings;
constructor Create; virtual;
end;
TMyClass = class of TMyObject;
constructor TMyObject.Create;
begin
inherited Create;
MyStrings := TStringList.Create;
end;
procedure Test;
var
C: TMyClass;
Instance: TObject;
begin
C := TMyObject;
Instance := C.Create;
end;
mjn
Software developer for Delphi and the Java platform, SCJP, SCJA
Updated on November 06, 2020Comments
-
mjn over 3 years
How can I create an instance of an object using a class reference, and ensure that the constructor is executed?
In this code example, the constructor of TMyClass will not be called:
type TMyClass = class(TObject) MyStrings: TStrings; constructor Create; virtual; end; constructor TMyClass.Create; begin MyStrings := TStringList.Create; end; procedure Test; var Clazz: TClass; Instance: TObject; begin Clazz := TMyClass; Instance := Clazz.Create; end;
-
mjn about 15 yearsOk, if I understand correctly this means that if I want to build a generic object factory with Delphi, I need to assign "class of TMyClass" to a variable - but this seems not possible.
-
Alex about 15 yearsIf you want to construct object of certain type, then you need to have class type information. If you have no class info - you can not construct an object of this type. Quite obvious ;)
-
mjn about 15 yearsVery good idea, it is a virtual method in TObject so I do not need to add any synthetic new root class. +1 for this idea.
-
bummi over 9 yearsThis is not exactly what the question is asking for.
-
alijunior over 9 yearsI was faced with this same problem, when I pass the class reference in the constructor of some list manager, specifying what kind of child it was populate the list, and when the list created the children, only the parent base class of children's contructor was called... the contructor of children items wasn't... the only solution for me, because constructor must have parameters, was declare an abstract method, call it in parent constructor, and override in child classes. This workaround may can help someone faced with our same problem... Best regards!