2013-04-30 2 views
8

Ho un oggetto complesso da copiare in profondità (un sacco di matrici, oggetti, puntatori, livelli di livelli di ereditarietà, centinaia di membri di vari tipi e altro), e ricrearlo tramite il metodo Assegna di Delphi è non produttivo e molto probabilmente troppo complesso.Copia di oggetti in delphi

Ho cercato Rtti e sembra una buona opzione, ma finora non sono riuscito a coprire tutti gli scenari possibili. Non voglio perdere così tanto tempo e sperare di trovare un buon esempio. Sfortunatamente, non sono riuscito a trovarne uno. Quello che ho fatto fino ad ora è passare attraverso tutti gli TRttiField nell'oggetto con un loop (TRttiType.GetFields()) e provare ad assegnare tutto usando i puntatori basati sui valori TTypeKind. (tkPointer, tkClass, tkClassRef ...)

Ho trovato un esempio JSON/Marshalling ma non è riuscito a copiare in profondità il mio oggetto complesso; Ho avuto un errore;

interna: Tipo tkPointer non è attualmente supportata

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

C'è qualcosa a Delfi vicino al C# serializzazione binario e la creazione di una copia completa utilizzando un flusso di memoria. O c'è un esempio buono e semplice di Delphi che fa una copia profonda con RTTI o JSON/Marshalling che funzionerebbe con gli oggetti più complessi?

+0

Jerry, questa classe eredita già TPersistent e Assign viene sovrascritto. Dovrei assegnare manualmente centinaia di oggetti tra loro a meno che non ci sia un modo automatico per farlo. (Ho provato a chiamare ereditato Assign e ha generato un errore tipo "Impossibile assegnare MyObject a MyObject". Ciò stava accadendo anche se stavo controllando il tipo di oggetto corretto prima di chiamare Assign.) – Alex

+3

Centinaia di membri? Sembra che tu abbia bisogno di dimagrire questo cattivo ragazzo. Per quello che vale, ci sono centinaia di domande sulla persistenza qui su SO. Un sacco di risposte già là fuori. –

+2

No, "Assign' non funziona in questo modo. ** Dovresti sostituire "Assegna" e fornire un mezzo per copiare – OnTheFly

risposta

5

In poche parole si non è possibile utilizzare RTTI per semplificare copia completa (sarà un modo più complicato e soggetto a errori rispetto all'utilizzo classico di override assegnare)

Quindi è necessario guardare più da vicino a TPersistent ei suoi oggetti figlio e correttamente l'override Assegna, assignTo metodi (non c'è modo più semplice)

0

Alex ho avuto lo stesso problema come voi, ho rotto la testa un po 'e ho scritto il seguente codice che ha risposto mia problema, pieno di speranza incontro anche i tuoi o altri.

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;