2011-12-28 3 views
4

D2010でRTTIを使用してオブジェクトを複製しようとしています。ここに私の試みはこれまでのところです:Delphi:RTTIを使用してインスタンス化されたオブジェクトに対して、オーバーライドされたメソッドが呼び出されない

uses SysUtils, TypInfo, rtti; 
type 
    TPerson = class(TObject) 
    public 
    Name: string; 
    destructor Destroy(); Override; 
    end; 
destructor TPerson.Destroy; 
begin 
    WriteLn('A TPerson was freed.'); 
    inherited; 
end; 
procedure CloneInstance(SourceInstance: TObject; DestinationInstance: TObject; Context: TRttiContext); Overload; 
var 
    rSourceType:  TRttiType; 
    rDestinationType: TRttiType; 
    rField:   TRttiField; 
    rSourceValue:  TValue; 
    Destination:  TObject; 
    rMethod:   TRttiMethod; 
begin 
    rSourceType := Context.GetType(SourceInstance.ClassInfo); 
    if (DestinationInstance = nil) then begin 
    rMethod := rSourceType.GetMethod('Create'); 
    DestinationInstance := rMethod.Invoke(rSourceType.AsInstance.MetaclassType, []).AsObject; 
    end; 
    for rField in rSourceType.GetFields do begin 
    if (rField.FieldType.TypeKind = tkClass) then begin 
     // TODO: Recursive clone 
    end else begin 
     // Non-class values are copied (NOTE: will cause problems with records etc.) 
     rField.SetValue(DestinationInstance, rField.GetValue(SourceInstance)); 
    end; 
    end; 
end; 
procedure CloneInstance(SourceInstance: TObject; DestinationInstance: TObject); Overload; 
var 
    rContext:  TRttiContext; 
begin 
    rContext := TRttiContext.Create(); 
    CloneInstance(SourceInstance, DestinationInstance, rContext); 
    rContext.Free(); 
end; 
var 
    Original:  TPerson; 
    Clone:  TPerson; 
begin 
    ReportMemoryLeaksOnShutdown := true; 
    Original := TPerson.Create(); 
    CloneInstance(Original, Clone); 
    Clone.Free(); 
    Original.Free(); 
    ReadLn; 
end. 

少しがっかり、私は「A TPersonが解放された複数の発生を見ません「。

オーバーライドされたデストラクタを呼び出すのを手伝ってもらえますか?(そして、なぜそれが呼び出されないのかを説明してください)それは先のタイプを知る必要がありますが、最初の場所。)ありがとう!

答えて

5

コードにいくつかの問題があります。

クローン変数をnilに初期化しません。私のマシン上では、値が渡された値がnon-nilであったため、クローンが作成されていないため、上位のCloneInstanceメソッドで違反にアクセスしました。

varとして宣言されたDestinationInstanceパラメータがありません。これは、上位CloneInstanceメソッドのインスタンス化が呼び出し側に戻ってこないことを意味します。パラメータにvarを追加すると、問題が解決されます。プログラムのメインメソッドからCloneInstanceを呼び出すときにTObject(Clone)を使用する必要があります。または、Delphiは「これらのパラメータで呼び出すことができるオーバーロードされたメソッドはありません」という文句を言います。これは、varパラメータが、宣言された型を渡すためです。

私はあなたのコードを変更:

uses 
    SysUtils, 
    TypInfo, 
    rtti; 

type 
    TPerson = class(TObject) 
    public 
    Name: string; 
    constructor Create; 
    destructor Destroy(); Override; 
    end; 

constructor TPerson.Create; 
begin 
    WriteLn('A TPerson was created'); 
end; 

destructor TPerson.Destroy; 
begin 
    WriteLn('A TPerson was freed.'); 
    inherited; 
end; 

procedure CloneInstance(SourceInstance: TObject; var DestinationInstance: TObject; Context: TRttiContext); Overload; 
var 
    rSourceType:  TRttiType; 
    rDestinationType: TRttiType; 
    rField:   TRttiField; 
    rSourceValue:  TValue; 
    Destination:  TObject; 
    rMethod:   TRttiMethod; 
begin 
    rSourceType := Context.GetType(SourceInstance.ClassInfo); 
    if (DestinationInstance = nil) then begin 
    rMethod := rSourceType.GetMethod('Create'); 
    DestinationInstance := rMethod.Invoke(rSourceType.AsInstance.MetaclassType, []).AsObject; 
    end; 
    for rField in rSourceType.GetFields do begin 
    if (rField.FieldType.TypeKind = tkClass) then begin 
     // TODO: Recursive clone 
    end else begin 
     // Non-class values are copied (NOTE: will cause problems with records etc.) 
     rField.SetValue(DestinationInstance, rField.GetValue(SourceInstance)); 
    end; 
    end; 
end; 

procedure CloneInstance(SourceInstance: TObject; var DestinationInstance: TObject); Overload; 
var 
    rContext:  TRttiContext; 
begin 
    rContext := TRttiContext.Create(); 
    CloneInstance(SourceInstance, DestinationInstance, rContext); 
    rContext.Free(); 
end; 

var 
    Original:  TPerson; 
    Clone:  TPerson; 
begin 
    Clone := nil; 
    ReportMemoryLeaksOnShutdown := true; 
    Original := TPerson.Create(); 
    Original.Name := 'Marjan'; 

    CloneInstance(Original, TObject(Clone)); 
    Original.Name := 'Original'; 
    WriteLn('Original name: ', Original.Name); 
    WriteLn('Clone name: ', Clone.Name); 

    Clone.Free(); 
    Original.Free(); 
    ReadLn; 
end. 

私は、クローニング後の名前をチェックするだけでなく、作成されるインスタンスと数行の両方を参照するためにコンストラクタを追加しました。

A TPerson was created 
A TPerson was created 
Original name: Original 
Clone name: Marjan 
A TPerson was freed. 
A TPerson was freed. 
+0

ありがとうございます!それは魅力のように働く。ホーマー・シンプソンの言葉を引用すると:D'oh! (これは私の最初のことではないが、うまくいけば最後の、欠けている 'var'に噛まれる時間)。 – conciliator

+0

ああ、サイドノートに:クローンが明示的にnilに設定されていないにもかかわらずAVを取得していない。 – conciliator

+0

@conciliator:うん、明示的に設定しないと、その値が何であるかわからないことを意味します。何もない可能性があります。これは、あなたのコードが予期しない動作をする可能性があることを意味します。なぜなら、前回のCloneのアドレスで発生した値に依存するからです。そして、AVはいつもすぐに起こるわけではありません。クラスのフィールドを使用しないメソッドだけを呼び出すと、年齢を問わずうまくいく可能性があります。それで、あなたが後ろであなたを噛んだとき、あなたはそれがどこから来ているのか把握しようとしているひどい時を過すでしょう。 TLDR:常に変数を初期化します。 –

0

この場合の例でコンストラクタのためのソリューション(が、基本的にも使用可能)がthis answer

How can I create an Delphi object from a class reference and ensure constructor execution?であります...オプションではない可能性があります

+0

OPは、リンクされた回答(変形2)とまったく同じことをしているようですが、1つではなく2つのステートメントです。あなたは実際にリンクされた答えの中でCreateと同じ方法でDestroyを呼び出さなければなりませんか?私は確かに望んでいない。あるクラスが正しくインスタンス化されると、そのインスタンスのオブジェクト参照上の任意のメソッドを呼び出すことは、通常の規則に従うべきです。 (そしてキャストは*インスタンス化の後に使用されますが、正しいタイプの変数に対してDestroy(ugh)が呼び出されるのは正しいですが)。 –

関連する問題