2011-01-18 12 views
5

Sto serializzando e deserializzando un oggetto (discendente TComponent) utilizzando l'esempio nella sezione ComponentToString nel file della guida di Delphi. Questo è così posso memorizzare l'oggetto in un campo VARCHAR nel database.Posso creare un costruttore che deserializza una versione di stringa del mio oggetto?

Quando ho bisogno di istanziare una nuova istanza della mia classe da una stringa memorizzata nel database, posso farlo utilizzando un costruttore del modulo CreateFromString(AOwner: TComponent; AData: String)? O devo usare un metodo non di classe che restituisce un'istanza della mia classe componente?

Se posso utilizzare la versione del costruttore, come "mappare" il valore restituito di ReadComponent al "sé" che viene creato dal costruttore?

Ecco l'esempio deserializzazione dal file di aiuto:

function StringToComponentProc(Value: string): TComponent; 
var 
    StrStream:TStringStream; 
    BinStream: TMemoryStream; 
begin 
    StrStream := TStringStream.Create(Value); 
    try 
    BinStream := TMemoryStream.Create; 
    try 
     ObjectTextToBinary(StrStream, BinStream); 
     BinStream.Seek(0, soFromBeginning); 
     Result:= BinStream.ReadComponent(nil); 
    finally 
     BinStream.Free; 
    end; 
    finally 
    StrStream.Free; 
    end; 
end; 

risposta

6

In generale, sì, è possibile eseguire la deserializzazione di una stringa da parte di un costruttore e utilizzare tali informazioni per inizializzare la nuova istanza. Un semplice esempio di ciò sarebbe una classe con un singolo campo Integer. Passa una stringa al costruttore e chiama il costruttore StrToInt e inizializza il campo con il risultato.

Ma se l'unica funzione disponibile per la deserializzazione è quella che crea anche l'istanza, non è possibile utilizzarla dal costruttore perché in tal caso si finirà con due istanze quando ne si desidera solo una. Non c'è modo per un costruttore di dire: "Non importa, non costruisci un'istanza dopotutto ne ho già una da qualche altra parte".

Tuttavia, questa non è la situazione in cui ti trovi.Come dovresti sapere, TStream.ReadComponent ti consente di creare tu stesso l'istanza. Crea un'istanza della classe solo se non le hai già fornito un'istanza da utilizzare. Si dovrebbe essere in grado di scrivere il costruttore in questo modo:

constructor TLarryComponent.CreateFromString(const AData: string); 
var 
    StrStream, BinStream: TStream; 
begin 
    Create(nil); 
    StrStream := TStringStream.Create(AData); 
    try 
    BinStream := TMemoryStream.Create; 
    try 
     ObjectTextToBinary(StrStream, BinStream); 
     BinStream.Position := 0; 
     BinStream.ReadComponent(Self); 
    finally 
     BinStream.Free; 
    end; 
    finally 
    StrStream.Free; 
    end; 
end; 

Ci stiamo passando l'oggetto corrente, designato da Self, a ReadComponent. Il flusso ignorerà il nome della classe memorizzato nello stream e presuppone che l'oggetto corrente sia della classe corretta.

+2

È possibile che ci sia un piccolo bug nella tua implementazione? Penso che dovrebbe essere 'Create (nil)'; non 'inherited Create (nil);'. Con "ereditato" si perde la creazione di qualsiasi memoria di campo e proprietà introdotte da TLarryComponent. –

+1

Hai ragione. Pensavo che "ReadComponent" si sarebbe occupato di questo, ma ora capisco che non c'è modo di farlo. –

+0

Ora funziona perfettamente e fornisce una soluzione elegante al mio problema. –

3

Utilizzare una statica class function invece di un constructor:

type 
    TYourClass = class(TComponent) 
    public 
    class function CreateFromString(AOwner: TComponent; AData: String): TYourClass; static; 
    end; 

implementation 

class function TYourClass.CreateFromString(AOwner: TComponent; AData: String): TYourClass; 
begin 
    Result := (StringToComponentProc(AData) as TYourClass); 
    if AOwner <> nil then 
    AOwner.InsertComponent(Result); 
end; 

La parte AOwner potrebbe essere un problema, però, dal momento che ha TStream.ReadComponent nessun parametro per il proprietario.

C'è un altro modo dubbio su questo problema:

How can I specify the Owner of component read from a Delphi TStream?

Edit: Ho aggiornato il codice di esempio per includere il proprietario, anche.

Si noti che l'inserimento nell'elenco dei componenti del proprietario richiede un Name univoco o vuoto per il componente che viene inserito.

+0

In realtà, il problema del proprietario non è un problema. Sto facendo di questo un discendente di TComponent solo per avere accesso alle capacità di streaming automatico. In effetti non esiste una rappresentazione visibile dell'oggetto, non lo userò in fase di progettazione e ne gestirò la durata nel codice. Quindi posso lasciare il proprietario nullo. –

3

È possibile eseguire questa operazione con un metodo class (statico), ma non tramite constructor.
I costruttori di Delphis vengono chiamati dal compilatore intrinseco all'istanza appena creata, che è già parzialmente inizializzata (è della classe desiderata e l'istanza/l'archiviazione di campo è azzerata).

Se si vede la sorgente di TStream.ReadComponent, si scoprirà che la classe reale dei componenti viene letta dal flusso di origine all'inizio, quindi un'istanza vuota viene creata e riempita da RTTI dallo stream e restituita come risultato. Il che significa:

Per utilizzare TStream.ReadComponent, è necessario registrare la classe sul sistema di streaming di Delphis tramite RegisterClass.

+0

Grazie Viktor. Questa risposta contiene una nota importante su RegisterClass che probabilmente mi avrebbe graffiato la testa per un'ora se non avessi pensato di indicarlo. –

Problemi correlati