为什么Delphi在动态对象创建过程中调用了错误的构造函数?

时间:2017-05-24 17:30:12

标签: delphi dynamic

我遇到了在动态对象创建过程中调用错误的虚拟Create()方法的问题。调用父方法而不是后代方法。

我已经查看了这些帖子,但无法弄明白:
Delphi - Create class from a string

Exploring TRTTIType and Descendants

Can I pass a Class type as a procedure parameter

Class_References

我有以下课程:

TCellObj = class(TPhysicsObj)
  ...
  public
    constructor Create(RgnMgr : TObject); virtual; //RgnMgr should be TRgnManager
    destructor Destroy;
    ...
  end;

  TCellObjClass = Class of TCellObj;

--------------------------------

 TCellTrialAObj = class(TCellObj)
    ...
    public
      ...
      constructor Create(RgnMgr : TObject); virtual; //RgnMgr should be TRgnManager
  end;
--------------------------------

TRgnManager = class (TObject)
  ... 
  public
    function NewCell(ClassRef : TCellObjClass) : TCellObj; 
    ...
  end;

  ....

  function TRgnManager.NewCell(ClassRef : TCellObjClass) : TCellObj;
  var CellObj : TCellObj;

  begin
    CellObj := ClassRef.Create(Self);
    CellObj.DefaultInitialize;
    CellObj.Color := TAlphaColors.Slategray;
    FCellsList.Add(CellObj);  //This will own objects.
    SetSelection(CellObj);
    Result := CellObj;
  end;

最后,我通过以下行开始动态对象创建过程:

RgnManager.NewCell(TCellTrialAObj);

我的目标是让TRgnManager.NewCell根据作为参数传入的派生类创建TCellObj的任何后代。我会在使用过程中将结果类型化为适当的类类型。

当我在NewCell中使用调试器逐步执行代码时,“评估/修改”工具会按预期告诉我ClassRef = TCellTrialAObj。

但是当我进入ClassRef.Create(self)行时,它会转到 TCellObj .Create(),而不是TCellTrialAObj.Create(),正如我所料。这是我不理解的部分。

将结果分配给CellObj后,评估/修改工具告诉我CellObj.ClassName ='TCellTrialAObj';

所以ClassRef是TCellTrialAObj,那么为什么没有Create()函数调用TCellTrialAObj.Create()??

提前致谢。

P.S。我使用的是Embarcadero®Delphi10 Seattle版本23.0.22248.5795

附录

我使用上面链接中的示例拼凑了下面这个函数。它似乎工作,并根据需要调用TCellTrialAObj.Create。但我并不确切地知道如何,为什么,或者我是否真的做得对。谁能解释一下?

  function TRgnManager.NewCell(ClassRef : TCellObjClass) : TCellObj;
  var CellObj : TCellObj;
      RT : TRttiType;
      C : TRttiContext;
      T : TRttiInstanceType;
      V : TValue;

  begin
    C := TRttiContext.Create;
    T := (C.GetType(ClassRef) as TRttiInstanceType);
    V := T.GetMethod('Create').Invoke(T.metaClassType,[self]);
    C.Free;
    CellObj := V.AsObject as TCellObj;

    //CellObj := ClassRef.Create(Self);
    CellObj.DefaultInitialize;
    CellObj.Color := TAlphaColors.Slategray;
    FCellsList.Add(CellObj);  //This will own objects.
    SetSelection(CellObj);
    Result := CellObj;
  end;

1 个答案:

答案 0 :(得分:4)

编译器警告将帮助您。在编译时,您可能会注意到您收到警告Method 'Create' hides virtual method of base type 'TCellObj'。这是因为当我们推断您希望它为TCellTrialAObj时,您已将后代virtual的构造函数声明为override

这里一个最小的例子演示了你想要的功能。

program Project1;

{$APPTYPE CONSOLE}

type
  TCellObj = class
    public
      constructor Create; virtual;
  end;

  TCellObjClass = Class of TCellObj;

  TCellTrialAObj = class(TCellObj)
    public
      constructor Create; override;
  end;

constructor TCellObj.Create;
begin
  WriteLn('TCellObj');
end;

constructor TCellTrialAObj.Create;
begin
  WriteLn('Calling base constructor...');
  inherited;
  WriteLn('...and now in TCellTrialAObj constructor');
end;

function NewCell(ClassRef : TCellObjClass) : TCellObj;
var
  CellObj : TCellObj;
begin
  CellObj := ClassRef.Create;;
  Result := CellObj;
end;

var
  LCellObj : TCellObj;
begin
  LCellObj := NewCell(TCellTrialAObj);
  ReadLn;
end.

另外,在这里,您使用注释来建议类型限制:

 constructor Create(RgnMgr : TObject); virtual; //RgnMgr should be TRgnManager

然而,有可能做出TRgnMgr类的前向声明并在以后完全定义它,允许您包含更强大的正式类型限制。

  TRgnMgr = class;  { Declare type... }

  TCellObj = class
    public
      constructor Create(RgnMgr : TRgnMgr); virtual;
  end;

  TCellObjClass = Class of TCellObj;

  TCellTrialAObj = class(TCellObj)
    public
      constructor Create(RgnMgr : TRgnMgr); override;
  end;

  TRgnMgr = class  { but define it later }
    private
      FFoo : integer;
  end;