Delphi:未为使用RTTI实例化的对象调用重写方法

Delphi:未为使用RTTI实例化的对象调用重写方法,delphi,delphi-2010,rtti,Delphi,Delphi 2010,Rtti,我正在尝试在D2010中使用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;

我正在尝试在D2010中使用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.
有点令人失望的是,对于输出(通过单步执行程序确认),我没有看到超过一次出现“一个TPerson被释放了”。只有原始的被重写的析构函数销毁

有人能帮我调用重写的析构函数吗?(也许还可以解释为什么不首先调用它。)谢谢!

一个示例解决方案(对于构造函数,但在本例中基本上也可用)位于


但是,它需要知道目标类型…这可能不是一个选项

代码中的两个问题

您没有将Clone变量初始化为nil。这在我的计算机上导致了上层CloneInstance方法中的访问冲突,因为传入的值为非nil,因此未创建克隆

您没有将DestinationInstance参数声明为var。这意味着上层CloneInstance方法中的实例化不会返回调用方。向参数添加
var
可以解决此问题。您确实需要使用
TObject(Clone)
在从程序的主方法调用CloneInstance时,或者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.

OP似乎在做与链接答案(变体2)完全相同的事情,尽管是在两条语句中,而不是在一条语句中。你真的必须像链接答案中的Create那样调用Destroy吗?我当然希望不是。一旦一个类被正确实例化,对该实例的对象引用调用任何方法都应该遵循正常规则。(虽然对正确类型的变量调用Destroy(ugh)是正确的,但是在实例化之后使用强制转换)。非常感谢!它就像一个符咒一样工作。引用荷马·辛普森的话:噢!(这不是我第一次,但希望是最后一次,被缺少的“var”咬伤了)。噢,顺便说一句:即使克隆没有显式地设置为nil,我也不会得到AV。@conciliator:不显式地设置它意味着你不知道它的值是什么,它可以是nil,也可以是任何值。这意味着你的代码可能表现得不可预测,因为它将取决于克隆的ad上恰好出现的值穿之前的衣服。而且,AV并不总是立即发生。如果你只调用不使用类的任何字段的方法,你可以很长时间都很好!那么当它真的从背后咬你的时候,你将很难找到它的来源。TLDR:总是初始化你的变量。你是绝对正确的,应该养成习惯初始化所有变量。再次感谢。