web-dev-qa-db-ja.com

クラス参照からDelphiオブジェクトを作成し、コンストラクターを確実に実行するにはどうすればよいですか?

クラス参照を使用してオブジェクトのインスタンスを作成し、コンストラクターが確実に実行されるようにするにはどうすればよいですか?

このコード例では、TMyClassのコンストラクターは呼び出されません。

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;
20
mjn

これを使って:

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;

または、(「TMyClassのクラス」の代わりに)TMyClassへの型キャストを使用することもできます。

26
Alex

アレクサンダーの解決策は素晴らしいものですが、特定の状況では十分ではありません。実行時にTClass参照を格納し、後で任意の数のインスタンスを取得できるTClassFactoryクラスを設定するとします。

このようなクラスファクトリは、保持しているクラスの実際のタイプについて何も知らないため、それらを対応するメタクラスにキャストすることはできません。このような場合に正しいコンストラクターを呼び出すには、次のアプローチが機能します。

まず、簡単なデモクラスが必要です(パブリックフィールドは気にしないでください。デモンストレーション用です)。

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;

ここで、純粋にRTTIによって、正しいコンストラクター呼び出しを使用して、THuman型のオブジェクトをインスタンス化します。

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;

オブジェクト「human1」と「human2」がゼロに初期化されていることがわかります。つまり、Name = ''とAge = 0ですが、これは私たちが望んでいることではありません。代わりに、オブジェクトhuman3は、THumanのコンストラクターで提供されるデフォルト値を保持します。

ただし、このメソッドでは、クラスにパラメーターではないコンストラクターメソッドが必要であることに注意してください。上記のすべては私が考案したものではありませんが、 Rob Love's Tech Corner で見事にそしてはるかに詳細に説明されています(たとえば、c.Freeの部分)。

22
Malte

AfterConstructionのオーバーライドがオプションかどうかを確認してください。

11
Stijn Sanders

コードが少し変更されました:

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;
6
Ondrej Kelle

基本クラスで抽象メソッドを作成し、それをコンストラクターで呼び出し、クラス参照から作成されたときに実行される子クラスでオーバーライドできます。

0
alijunior