2013-04-30 10 views
8

Ich habe ein komplexes Objekt zutiefst zu kopieren (viele Arrays, Objekte, Zeiger, Schichten von Vererbungsebenen, Hunderte von Mitgliedern verschiedener Typen und mehr), und neu erstellen es durch Delphi Assign-Methode ist nicht produktiv und wahrscheinlich zu komplex.Kopieren von Objekten in Delphi

Ich habe mir Rtti angesehen und es scheint eine gute Option zu sein, aber bisher konnte ich nicht alle möglichen Szenarien abdecken. Ich möchte nicht so viel Zeit verschwenden und hoffen, ein gutes und einfaches Beispiel zu finden. Leider konnte ich noch keinen finden. Was ich bisher gemacht habe, ist, alle TRttiField im Objekt mit einer Schleife (TRttiType.GetFields()) durchlaufen und versuchen, alles mit Zeigern basierend auf TTypeKind Werte zuweisen. (tkPointer, tkClass, tkClassRef ...)

Ich habe ein JSON/Marshalling-Beispiel gefunden, aber es konnte mein komplexes Objekt nicht tief kopieren; Ich habe Fehler;

Intern: Typ tkPointer wird derzeit nicht unterstützt

http://www.yanniel.info/2012/02/deep-copy-clone-object-delphi.html

Gibt es etwas in Delphi der Nähe von C# binäre Serialisierung und eine tiefe Kopie Erstellen eines Speicher-Stream verwendet wird. Oder gibt es ein gutes und einfaches Beispiel, das Sie in Delphi kennen, wenn Sie tiefe Kopien mit RTTI oder JSON/Marshalling machen, die mit den komplexesten Objekten funktionieren würden?

+0

Jerry, diese Klasse erbt bereits TPersistent und der Assign wird überschrieben. Ich müsste manuell Hunderte von Objekten zuweisen, es sei denn, es gibt eine automatische Möglichkeit, dies zu tun. (Ich habe versucht, vererbte Assign aufzurufen, und es wurde ein Fehler wie "Kann MyObject nicht zu MyObject zuweisen" ausgelöst. Dies passierte, obwohl ich vor dem Aufruf von Assign nach dem korrekten Objekttyp suchte.) – Alex

+3

Hunderte von Mitgliedern? Klingt so, als müsste man diesen bösen Jungen etwas abmildern. Für was es wert ist, gibt es Hunderte von Persistenzfragen hier auf SO. Viele Antworten schon da draußen. –

+2

Nein, 'Assign' funktioniert nicht so. ** Sie ** sollten 'AssignTo' überschreiben und einen Mittelwert für das Kopieren angeben – OnTheFly

Antwort

5

In wenigen Worten Sie nicht rtti verwenden tiefe Kopie zu vereinfachen (es wird ein Weg komplizierter und Fehler anfällig als Überschreibung klassische assign verwenden)

Sie müssen also schauen näher an TPersistent und seine untergeordneten Objekte und richtig zuordnen, AssignTo Methoden außer Kraft setzen (es gibt keine einfachere Art und Weise)

0

Alex ich das gleiche Problem wie du hatte, brach ich den Kopf ein wenig und schrieb den folgenden Code, der meine beantwortet Problem, hoffnungsvoll Treffen Sie auch Ihre oder andere.

function TModel.Clone(pObj:TObject): TObject; 
procedure WriteInField(pField:TRttiField; result, source:Pointer); 
var 
    Field:TRttiField; 
    Val:TValue; 
    Len, I :Integer; 
    tp:TRttiType; 
    ctx:TRttiContext; 
begin 
    if not pField.GetValue(source).IsEmpty then 
    case pField.FieldType.TypeKind of 
     TTypeKind.tkRecord: 
     begin 
      for Field in pField.FieldType.GetFields do 
      WriteInField(Field, PByte(result)+pField.Offset, pField.GetValue(source).GetReferenceToRawData); 
     end; 
     TTypeKind.tkClass: 
     begin 
      Val:=Self.Clone(pField.GetValue(source).AsObject); 
      if Assigned(TObject(pField.GetValue(result).AsObject)) then 
      pField.GetValue(result).AsObject.Free; 

      pField.SetValue(result,Val); 
     end; 
     TTypeKind.tkDynArray: 
     begin 
      Len := pField.GetValue(source).GetArrayLength; 
      for I := 0 to Len -1 do 
      case pField.GetValue(source).GetArrayElement(I).Kind of 
       TTypeKind.tkRecord: 
       begin 
       tp:=ctx.GetType(pField.GetValue(source).GetArrayElement(I).TypeInfo); 
       for Field in tp.GetFields do 
        WriteInField(Field,PByte(result)+Field.Offset, pField.GetValue(source).GetReferenceToRawData); 

       end; 
       TTypeKind.tkClass: 
       begin 
       Val:=Self.Clone(pField.GetValue(source).GetArrayElement(I).AsObject); 
       DynArraySetLength(PPointer(PByte(result)+pField.Offset)^,pField.GetValue(source).TypeInfo,1,@Len); 
       pField.GetValue(result).SetArrayElement(I,Val); 
       end; 
      else 
       DynArraySetLength(PPointer(PByte(result)+pField.Offset)^,pField.GetValue(source).TypeInfo,1,@Len); 
       pField.GetValue(result).SetArrayElement(I, pField.GetValue(source).GetArrayElement(I)); 
      end; 

     end; 
    else 
     pField.SetValue(result,pField.GetValue(source)); 
    end; 
end; 
var 
    Context: TRttiContext; 
    IsComponent, LookOutForNameProp: Boolean; 
    RttiType: TRttiType; 
    Method: TRttiMethod; 
    MinVisibility: TMemberVisibility; 
    Params: TArray<TRttiParameter>; 
    PropFild: TRttiField; 
    Fild: TRttiField; 
    SourceAsPointer, ResultAsPointer: Pointer; 
    ObjWithData:TObject; 
    Value:TValue; 

begin 
try 
    if Assigned(pObj) then 
    ObjWithData := pObj 
    else 
    ObjWithData := Self; 
    RttiType := Context.GetType(ObjWithData.ClassType); 
    //find a suitable constructor, though treat components specially 
    IsComponent := (ObjWithData is TComponent); 
    for Method in RttiType.GetMethods do 
    if Method.IsConstructor then 
    begin 
     Params := Method.GetParameters; 
     if Params = nil then Break; 
     if (Length(Params) = 1) and IsComponent and 
     (Params[0].ParamType is TRttiInstanceType) and 
     SameText(Method.Name, 'Create') then Break; 
    end; 
    if Params = nil then 
    Result := Method.Invoke(ObjWithData.ClassType, []).AsObject 
    else 
    Raise Exception.CreateFmt('Object Invalid to clone : ''%s''', [ObjWithData.ClassName]); 
    try 

    //loop through the props, copying values across for ones that are read/write 
    Move(ObjWithData, SourceAsPointer, SizeOf(Pointer)); 
    Move(Result, ResultAsPointer, SizeOf(Pointer)); 
    for PropFild in RttiType.GetFields do 
     WriteInField(PropFild,ResultAsPointer,SourceAsPointer); 

    except 
    Result.Free; 
    raise; 
    end; 
finally 
    ObjWithData := nil; 
end; 

end;