2010-01-12 10 views
5

Ho bisogno di un modo per scrivere una procedura generica per agire su un tipo di oggetto o uno dei suoi discendenti.come evitare in modo sicuro Delphi Errore: "i tipi di parametri formali ed effettivi devono essere identici"

Il mio primo tentativo è stato quello di dichiarare

procedure TotalDestroy(var obj:TMyObject); 

, ma quando lo si utilizza con un oggetto discendente

type TMyNewerObject = class(TMyObject); 
var someNewerObject: TMyNewerObject; 

TotalDestroy(someNewerObject); 

ottengo l'errore famigerate "tipi di parametri formali e attuali devono essere identiche"

Così, mentre stavo cercando una soluzione, ho esaminato il codice sorgente della procedura del sistema Delphi FreeAndNil. E ho trovato questa dichiarazione impressionante, insieme a questo sorprendente commento

{ FreeAndNil frees the given TObject instance and 
    sets the variable reference to nil. 
    Be careful to only pass TObjects to this routine. } 

procedure FreeAndNil(var Obj); 

Evita il tipo di controllo degli errori, ma utilizza alcuna rete di sicurezza.

La mia domanda è ... esiste un modo sicuro per verificare il tipo di parametro var non tipizzato?

o in altre parole, è possibile migliorare questo codice sorgente Delphi in modo che l'avviso non sia necessario?

procedure FreeAndNil(var Obj); 
var 
    Temp: TObject; 
begin 
    Temp := TObject(Obj); 
    Pointer(Obj) := nil; 
    Temp.Free; 
end; 
+4

Non pensi che la procedura sarebbe stata scritta in modo diverso se ci fosse un modo per farlo? –

+0

Non lo so, è per questo che lo sto chiedendo. –

+3

PA, la risposta alla domanda di Lasse è * sì *. Se * ci fosse * un modo migliore, la funzione della libreria l'avrebbe usata. Poiché la funzione di libreria * non * la usa, possiamo tranquillamente concludere che non esiste un modo migliore. –

risposta

7

Ho scritto su questo prima, con un esempio molto simile a Lasse's:

A meno che non si sta scrivendo un'istruzione di assegnazione per variazione del valore dell'ingresso parametro stesso, e non solo una delle sue proprietà, non si dovrebbe passare un parametro per riferimento in primo luogo.

Se si è scrivere un'istruzione di assegnazione per modificare il valore del parametro, quindi il messaggio del compilatore è realmente vero e si dovrebbe prestare attenzione.

Uno dei motivi per cui è necessario ignorare l'errore è quando si scrive una funzione come TApplication.CreateForm. Il suo compito è modificare il valore del parametro di input e il tipo del nuovo valore varia e non può essere determinato in fase di compilazione. Se si scrive una tale funzione, quindi l'unica opzione con Delphi è quella di utilizzare un parametro untyped var, e poi c'è onere supplementare sia il chiamante e il ricevitore per assicurarsi che tutto va bene. Il chiamante deve assicurarsi che passi una variabile che sia in grado di contenere valori di qualunque tipo la funzione inserirà, e la funzione deve assicurarsi che memorizzi un valore di un tipo compatibile con quello richiesto dal chiamante.

Nel caso di CreateForm, il chiamante passa un valore letterale di riferimento di classe e una variabile di quel tipo di classe. La funzione crea un'istanza della classe e memorizza il riferimento nella variabile.

Non penso molto allo CreateForm o allo FreeAndNil, in gran parte a causa del modo in cui i loro parametri non tipizzati sacrificano la sicurezza del tipo in cambio di convenienza relativamente piccola. Non hai mostrato l'implementazione della tua funzione TotalDestroy, ma ho il sospetto che il suo parametro var alla fine fornisca la stessa bassa utilità come in quelle altre due funzioni. Vedere i miei articoli su entrambi:

+0

Come hai intuito in modo intelligente, il mio TotalDestroy esegue una ripulitura speciale di TMyObject e infine fa un FreeAndNil. –

+3

Se è necessaria una pulizia speciale per un oggetto 'TMyObject', prendere in considerazione di inserirlo nel distruttore. Non costringere i consumatori della tua classe a imparare un nuovo modo di distruggerlo quando plain old Free funziona bene per tutto il resto. –

+0

Grazie Rob. Prendo il tuo punto, non ho bisogno del mio TotalDestroy. Andrò solo con il mio distruttore Destroy che ho già. E lasciare che l'utente decida se lo desidera attraverso FreeAndNil oppure no. –

9

Esaminiamo ciò che si vuole fare.

Si desidera chiamare un metodo che prende X, passando un oggetto di tipo Y, dove Y è un discendente di X. L'intoppo, il parametro è un parametro "var".

Analizziamo cosa potresti fare se fosse possibile.

type 
    TBase = class 
    end; 
    TDescendant = class(TBase) 
    end; 

procedure Fiddle(var x: TBase); 
begin 
    x := TDescendant.Create; 
end; 

type 
    TOtherDescendant = class(TBase) 
    end; 

var a: TOtherDescendant; 
a := TOtherDescendant.Create; 
Fiddle(a); 

Uh-oh, ora a non contiene più un'istanza di TOtherDescendant, contiene un esempio di TDescendant. Questo probabilmente è una sorpresa per il codice che segue la chiamata.

Non si deve prendere in considerazione solo ciò che si intende a che fare con la sintassi si propone, ma in modo efficace ciò che si potrebbe che fare con la sintassi.

Si consiglia di leggere il post di blog eccellente di Eric Lipperts su problemi simili in. NET, trovato qui: Why do ref and out parameters not allow type variation?.

+0

grazie per la tua comprensione e per il link. Come ho commentato di seguito, la mia intenzione è quella di annullare definitivamente il riferimento. –

3

In aggiunta a ciò che ha scritto Lasse, che è abbastanza corretto, il più delle volte non si vuole passare un oggetto a un parametro var in ogni caso.

Un oggetto è un tipo di riferimento. Quello che vedi come l'oggetto è in realtà un riferimento ad esso. Si desidera solo passare un oggetto riferimento a un parametro var se si desidera modificare l'oggetto per un nuovo oggetto. Se vuoi solo essere in grado di modificare i membri dell'oggetto, puoi farlo semplicemente passando ad un parametro normale. Effettuare la chiamata al metodo utilizzando il parametro TMyObject anziché il parametro var TMyObject e dovrebbe funzionare.

Ovviamente, se davvero stai sostituendo l'oggetto, allora sentiti libero di ignorare tutto questo e di vedere la risposta di Lasse.

+0

D'accordo, non ho pensato a quell'angolo. Abbastanza corretto, non hai bisogno di "var" se tutto ciò che vuoi è il riferimento. –

+0

infatti, volevo solo annullarlo, eseguendo un po 'di pulizia speciale prima. –

Problemi correlati