【问题标题】:How to copy the properties of one class instance to another instance of the same class?如何将一个类实例的属性复制到同一类的另一个实例?
【发布时间】:2011-12-30 18:52:22
【问题描述】:

我想复制一个课程。我复制该类的所有属性就足够了。是否可以:

  1. 循环遍历类的所有属性?
  2. 将每个属性分配给另一个属性,例如a.prop := b.prop?

getter 和 setter 应该注意底层的实现细节。

编辑: 正如弗朗索瓦指出的那样,我的问题措辞不够仔细。我希望问题的新措辞更好

解决方案: Linas 得到了正确的解决方案。在下面找到一个小型演示程序。派生类按预期工作。直到有几个人指出我之前,我才知道新的 RTTI 可能性。非常有用的信息。谢谢大家。

  unit properties;

  interface

  uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
       Dialogs, StdCtrls,
       RTTI, TypInfo;

  type
     TForm1 = class(TForm)
        Memo1: TMemo;
        Button0: TButton;
        Button1: TButton;

        procedure Button0Click(Sender: TObject);
        procedure Button1Click(Sender: TObject);

     public
        procedure GetObjectProperties (AObject: TObject; AList: TStrings);
        procedure CopyObject<T: class>(ASourceObject, ATargetObject: T);
     end;

     TDemo = class (TObject)
     private
        FIntField: Int32;

        function  get_str_field: string;
        procedure set_str_field (value: string);

     public
        constructor Create; virtual;

        property IntField: Int32 read FIntField write FIntField;
        property StrField: string read get_str_field write set_str_field;
     end; // Class: TDemo //

     TDerived = class (TDemo)
     private
        FList: TStringList;

        function  get_items: string;
        procedure set_items (value: string);

     public
        constructor Create; override;
        destructor Destroy; override;
        procedure add_string (text: string);

        property Items: string read get_items write set_items;
     end;

  var Form1: TForm1;

  implementation

  {$R *.dfm}

  procedure TForm1.GetObjectProperties(AObject: TObject; AList: TStrings);
  var ctx: TRttiContext;
      rType: TRttiType;
      rProp: TRttiProperty;
      AValue: TValue;
      sVal: string;

  const SKIP_PROP_TYPES = [tkUnknown, tkInterface];

  begin
     if not Assigned(AObject) and not Assigned(AList) then Exit;

     ctx := TRttiContext.Create;
     rType := ctx.GetType(AObject.ClassInfo);
     for rProp in rType.GetProperties do
     begin
        if (rProp.IsReadable) and not (rProp.PropertyType.TypeKind in SKIP_PROP_TYPES) then
        begin
           AValue := rProp.GetValue(AObject);
           if AValue.IsEmpty then
           begin
              sVal := 'nil';
           end else
           begin
              if AValue.Kind in [tkUString, tkString, tkWString, tkChar, tkWChar]
                 then sVal := QuotedStr(AValue.ToString)
                 else sVal := AValue.ToString;
           end;
           AList.Add(rProp.Name + '=' + sVal);
        end;
     end;
  end;

  procedure TForm1.CopyObject<T>(ASourceObject, ATargetObject: T);
  const
    SKIP_PROP_TYPES = [tkUnknown, tkInterface, tkClass, tkClassRef, tkPointer, tkProcedure];
  var
    ctx: TRttiContext;
    rType: TRttiType;
    rProp: TRttiProperty;
    AValue, ASource, ATarget: TValue;
  begin
    Assert( Assigned(ASourceObject) and Assigned(ATargetObject) , 'Both objects must be assigned');
    ctx := TRttiContext.Create;
    rType := ctx.GetType(ASourceObject.ClassInfo);
    ASource := TValue.From<T>(ASourceObject);
    ATarget := TValue.From<T>(ATargetObject);

    for rProp in rType.GetProperties do
    begin
      if (rProp.IsReadable) and (rProp.IsWritable) and not (rProp.PropertyType.TypeKind in SKIP_PROP_TYPES) then
      begin
        //when copying visual controls you must skip some properties or you will get some exceptions later
        if SameText(rProp.Name, 'Name') or (SameText(rProp.Name, 'WindowProc')) then
          Continue;
        AValue := rProp.GetValue(ASource.AsObject);
        rProp.SetValue(ATarget.AsObject, AValue);
      end;
    end;
  end;

  procedure TForm1.Button0Click(Sender: TObject);
  var demo1, demo2: TDemo;
  begin
     demo1 := TDemo.Create;
     demo2 := TDemo.Create;
     demo1.StrField := '1023';

     Memo1.Lines.Add ('---Demo1---');
     GetObjectProperties (demo1, Memo1.Lines);
     CopyObject<TDemo> (demo1, demo2);

     Memo1.Lines.Add ('---Demo2---');
     GetObjectProperties (demo2, Memo1.Lines);
  end;

  procedure TForm1.Button1Click(Sender: TObject);
  var derivate1, derivate2: TDerived;
  begin
     derivate1 := TDerived.Create;
     derivate2 := TDerived.Create;
     derivate1.IntField := 432;
     derivate1.add_string ('ien');
     derivate1.add_string ('twa');
     derivate1.add_string ('drei');
     derivate1.add_string ('fjour');

     Memo1.Lines.Add ('---derivate1---');
     GetObjectProperties (derivate1, Memo1.Lines);
     CopyObject<TDerived> (derivate1, derivate2);

     Memo1.Lines.Add ('---derivate2---');
     GetObjectProperties (derivate2, Memo1.Lines);
  end;

  constructor TDemo.Create;
  begin
     IntField := 321;
  end; // Create //

  function TDemo.get_str_field: string;
  begin
     Result := IntToStr (IntField);
  end; // get_str_field //

  procedure TDemo.set_str_field (value: string);
  begin
     IntField := StrToInt (value);
  end; // set_str_field //

  constructor TDerived.Create;
  begin
     inherited Create;

     FList := TStringList.Create;
  end; // Create //

  destructor TDerived.Destroy;
  begin
     FList.Free;

     inherited Destroy;
  end; // Destroy //

  procedure TDerived.add_string (text: string);
  begin
     FList.Add (text);
  end; // add_string //

  function TDerived.get_items: string;
  begin
     Result := FList.Text;
  end; // get_items //

  procedure TDerived.set_items (value: string);
  begin
     FList.Text := value;
  end; // set_items //

  end. // Unit: properties //

【问题讨论】:

  • 你用的是什么Delphi版本?
  • 最近有一个问题与您的问题类似,那里的答案使用“新 RTTI”,因此需要 Delphi 2010 或更高版本,请参阅stackoverflow.com/q/8679735/723693
  • 显示到目前为止您已编码的内容。这是一个必不可少的输入。您的问题仍然含糊不清:例如这些属性是否已发布?这个类是 TPersistent 的后代吗?什么编译器版本?
  • "getter 和 setter 应该注意底层的实现细节。"这是您将使用的类的要求还是属性?
  • @Francois,问题应该是:如何将一个类的实例复制到另一个实例。使用德尔福 XE。我没有编写任何代码,因为我找不到有关复制 Delphi 属性的内容。因为所有的功能最终都可以在属性中找到,所以我只想复制一个类的所有属性,因此 getter 和 setter 的注释。没有关于父类的假设。我现在查看参考。

标签: delphi properties delphi-xe rtti


【解决方案1】:

试试这段代码(但我不建议复制可视组件的属性,因为那样你需要手动跳过一些属性):

uses
  Rtti, TypInfo;

procedure CopyObject<T: class>(ASourceObject, ATargetObject: T);

procedure TForm1.CopyObject<T>(ASourceObject, ATargetObject: T);
const
  SKIP_PROP_TYPES = [tkUnknown, tkInterface, tkClass, tkClassRef, tkPointer, tkProcedure];
var
  ctx: TRttiContext;
  rType: TRttiType;
  rProp: TRttiProperty;
  AValue, ASource, ATarget: TValue;
begin
  Assert( Assigned(ASourceObject) and Assigned(ATargetObject) , 'Both objects must be assigned');
  ctx := TRttiContext.Create;
  rType := ctx.GetType(ASourceObject.ClassInfo);
  ASource := TValue.From<T>(ASourceObject);
  ATarget := TValue.From<T>(ATargetObject);

  for rProp in rType.GetProperties do
  begin
    if (rProp.IsReadable) and (rProp.IsWritable) and not (rProp.PropertyType.TypeKind in SKIP_PROP_TYPES) then
    begin
      //when copying visual controls you must skip some properties or you will get some exceptions later
      if SameText(rProp.Name, 'Name') or (SameText(rProp.Name, 'WindowProc')) then
        Continue;
      AValue := rProp.GetValue(ASource.AsObject);
      rProp.SetValue(ATarget.AsObject, AValue);
    end;
  end;
end;

使用示例:

CopyObject<TDemoObj>(FObj1, FObj2);

【讨论】:

  • 我从列出所有属性的相同基本代码开始,但尚未找到复制属性的解决方案。感谢您的解决方案,效果很好。我不会想到应用泛型,这确实使它成为一个简洁的解决方案。我不打算复制视觉对象,只是一些自己创建的对象,但感谢您的警告。
【解决方案2】:

您的问题对我来说没有多大意义。

你真的想通过复制现有的类来创建一个新类吗?

或者您是否尝试将一个类的实例 A 深拷贝到同一类的另一个实例 B? 在这种情况下,请参阅this discussion about cloning in another SO question.

【讨论】:

    【解决方案3】:

    您没有提到您的 Delphi 版本,但这是一个好的开始。您需要探索允许您获取运行时类型信息的 Delphi RTTI。您必须为类型迭代源类,然后提供分配每种类型的方法。

    About RTTI

    如果您正在设计自己的简单类,则可以覆盖 assign 并在那里进行自己的属性分配。

    【讨论】:

    • 请不要隐藏链接(也包括 about.com 的框架),谢谢!
    猜你喜欢
    • 1970-01-01
    • 2011-04-06
    • 1970-01-01
    • 1970-01-01
    • 2014-02-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多