How can I create an Delphi object from a class reference and ensure constructor execution?

前端 未结 5 738
别那么骄傲
别那么骄傲 2020-12-01 04:26

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 wi

相关标签:
5条回答
  • 2020-12-01 05:05

    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;
    
    0 讨论(0)
  • 2020-12-01 05:09

    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.

    0 讨论(0)
  • 2020-12-01 05:14

    You can create an abstract method in base class, and call it in the constructor, and override in child classes to be executed when created from class reference.

    0 讨论(0)
  • 2020-12-01 05:29

    Please check if overriding AfterConstruction is an option.

    0 讨论(0)
  • 2020-12-01 05:30

    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").

    0 讨论(0)
提交回复
热议问题