Как скопировать свойства одного экземпляра класса в другой экземпляр того же класса? - PullRequest
1 голос
/ 30 декабря 2011

Я хочу продублировать класс. Достаточно, чтобы я скопировал все свойства этого класса. Можно ли:

  1. цикл через все свойства класса?
  2. присваивайте каждое свойство другому свойству, например a.prop := b.prop?

Методы получения и установки должны заботиться о базовых деталях реализации.

EDIT: Как отметил Франсуа, я недостаточно тщательно сформулировал свой вопрос. Надеюсь, новая формулировка вопроса лучше

РЕШЕНИЕ: Линас получил правильное решение. Найдите небольшую демонстрационную программу ниже. Производные классы работают как положено. Я не знал о новых возможностях 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 //

Ответы [ 3 ]

4 голосов
/ 31 декабря 2011

Попробуйте этот код (но я не советую копировать свойства визуальных компонентов, потому что тогда вам придется вручную пропустить некоторые свойства):

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);
1 голос
/ 31 декабря 2011

Вы не упомянули свою версию Delphi, но вот хорошее начало. Вы должны изучить Delphi RTTI, который позволяет вам получить информацию о типе времени выполнения. Вам нужно будет повторить свой исходный класс для типов, а затем предоставить метод для назначения каждого типа.

О RTTI

Если вы разрабатываете свои собственные простые классы, вы можете просто переопределить присвоение и выполнить там свои собственные присвоения свойств.

1 голос
/ 31 декабря 2011

Ваш вопрос для меня не имеет большого смысла.

Вы действительно пытаетесь создать новый класс, скопировав существующий?

Или вы пытаетесь сделать глубокую копию экземпляра A класса в другой экземпляр B того же класса?
В этом случае см. это обсуждение клонирования в другом вопросе SO.

...