La migliore pratica per eseguire l'istruzione TRY / FINALY nidificata
-
29-08-2019 - |
Domanda
Ciao. Qual è il modo migliore per eseguire istruzioni try & final nidificate in Delphi?
var cds1 : TClientDataSet;
cds2 : TClientDataSet;
cds3 : TClientDataSet;
cds4 : TClientDataSet;
begin
cds1 := TClientDataSet.Create(application );
try
cds2 := TClientDataSet.Create(application );
try
cds3 := TClientDataSet.Create(application );
try
cds4 := TClientDataSet.Create(application );
try
///////////////////////////////////////////////////////////////////////
/// DO WHAT NEEDS TO BE DONE
///////////////////////////////////////////////////////////////////////
finally
cds4.free;
end;
finally
cds3.free;
end;
finally
cds2.free;
end;
finally
cds1.free;
end;
end;
Potete suggerire un modo migliore per farlo?
Soluzione
come circa il seguente:
var cds1 : TClientDataSet;
cds2 : TClientDataSet;
cds3 : TClientDataSet;
cds4 : TClientDataSet;
begin
cds1 := Nil;
cds2 := Nil;
cds3 := Nil;
cds4 := Nil;
try
cds1 := TClientDataSet.Create(nil);
cds2 := TClientDataSet.Create(nil);
cds3 := TClientDataSet.Create(nil);
cds4 := TClientDataSet.Create(nil);
///////////////////////////////////////////////////////////////////////
/// DO WHAT NEEDS TO BE DONE
///////////////////////////////////////////////////////////////////////
finally
freeandnil(cds4);
freeandnil(cds3);
freeandnil(cds2);
freeandnil(Cds1);
end;
end;
Questo mantiene compatta, e cerca solo di liberare le istanze che sono stati creati. Non c'è davvero alcun bisogno di eseguire la nidificazione dal momento che ogni fallimento si tradurrà nel cadere a fine e l'esecuzione di tutte le pulizia nell'esempio che hai fornito.
Personalmente cerco di non nido all'interno lo stesso metodo ... con l'eccezione che è una prova / prova / eccetto / finalmente scenario. Se mi trovo a dover nido, quindi per me questo è un grande momento di pensare il refactoring in un'altra chiamata al metodo.
Modifica ripulito un po 'grazie alle osservazioni mghie e utku .
modifica cambiato la creazione dell'oggetto di non riferimento all'applicazione, come non necessario in questo esempio.
Altri suggerimenti
mi piacerebbe usare qualcosa di simile:
var
Safe: IObjectSafe;
cds1 : TClientDataSet;
cds2 : TClientDataSet;
cds3 : TClientDataSet;
cds4 : TClientDataSet;
begin
Safe := ObjectSafe;
cds1 := Safe.Guard(TClientDataSet.Create(nil)) as TClientDataSet;
cds2 := Safe.Guard(TClientDataSet.Create(nil)) as TClientDataSet;
cds3 := Safe.Guard(TClientDataSet.Create(nil)) as TClientDataSet;
cds4 := Safe.Guard(TClientDataSet.Create(nil)) as TClientDataSet;
///////////////////////////////////////////////////////////////////////
/// DO WHAT NEEDS TO BE DONE
///////////////////////////////////////////////////////////////////////
// if Safe goes out of scope it will be freed and in turn free all guarded objects
end;
Per l'implementazione dell'interfaccia vedi questo articolo , ma si può facilmente creare qualcosa di te stesso simile .
Modifica
Ho appena notato che nella Guardia articolo collegato () è una procedura. Nel mio codice che ho sovraccaricato le funzioni di Guardia () che restituiscono TObject, sopra il codice di esempio presuppone qualcosa di simile. Naturalmente con i generici codice molto meglio oggi è possibile ...
EDIT 2:
Se vi chiedete perché try ... finally viene completamente rimosso nel mio codice: E 'impossibile rimuovere i blocchi annidati senza introdurre la possibilità di perdite di memoria (quando distruttori sollevano eccezioni) o violazioni di accesso. Quindi è meglio utilizzare una classe di supporto, e lasciare che il conteggio dei riferimenti di interfacce prendere in consegna completamente. La classe di supporto in grado di liberare tutti gli oggetti custodisce, anche se alcuni dei distruttori sollevano eccezioni.
C'è un'altra variante del codice senza provare nidificato ... infine, che appena venuto in mente. Se non si creano i componenti con il parametro AOwner del costruttore set a zero, allora si può semplicemente utilizzare la gestione della durata che la VCL ti dà gratuitamente:
var
cds1: TClientDataSet;
cds2: TClientDataSet;
cds3: TClientDataSet;
cds4: TClientDataSet;
begin
cds1 := TClientDataSet.Create(nil);
try
// let cds1 own the other components so they need not be freed manually
cds2 := TClientDataSet.Create(cds1);
cds3 := TClientDataSet.Create(cds1);
cds4 := TClientDataSet.Create(cds1);
///////////////////////////////////////////////////////////////////////
/// DO WHAT NEEDS TO BE DONE
///////////////////////////////////////////////////////////////////////
finally
cds1.Free;
end;
end;
Sono un grande credente in piccolo codice (se non è troppo offuscato).
Se si vuole seguire questa (IMO) percorso brutto (gruppo trattamento effettuato con l'inizializzazione a zero per sapere se è necessaria liberazione), è almeno deve garantire che non si lascia un'eccezione in una delle distruttore impedisce di liberare il resto dei tuoi oggetti.
Qualcosa di simile:
function SafeFreeAndNil(AnObject: TObject): Boolean;
begin
try
FreeAndNil(AnObject);
Result := True;
except
Result := False;
end;
end;
var cds1 : TClientDataSet;
cds2 : TClientDataSet;
IsOK1 : Boolean;
IsOK2 : Boolean;
begin
cds1 := Nil;
cds2 := Nil;
try
cds1 := TClientDataSet.Create(nil);
cds2 := TClientDataSet.Create(nil);
///////////////////////////////////////////////////////////////////////
/// DO WHAT NEEDS TO BE DONE
///////////////////////////////////////////////////////////////////////
finally
IsOk2 := SafeFreeAndNil(cds2); // an error in freeing cds2 won't stop execution
IsOK1 := SafeFreeAndNil(Cds1);
if not(IsOk1 and IsOk2) then
raise EWhatever....
end;
end;
C'è un bel video su eccezioni nei costruttori e nei distruttori
Mostra alcuni esempi carini come:
var cds1 : TClientDataSet;
cds2 : TClientDataSet;
begin
cds1 := Nil;
cds2 := Nil;
try
cds1 := TClientDataSet.Create(nil);
cds2 := TClientDataSet.Create(nil);
///////////////////////////////////////////////////////////////////////
/// DO WHAT NEEDS TO BE DONE
///////////////////////////////////////////////////////////////////////
finally
freeandnil(cds2); //// what has if there in an error in the destructor of cds2
freeandnil(Cds1);
end;
end;
Cosa succede se si verifica un errore nel distruttore di cds2
Cds1 non verrà distrutto
MODIFICARE
Un'altra buona risorsa è:
Eccellente video di Jim McKeeth Gestione delle eccezioni ritardata nell'intervallo di codici III dove parla di problemi nella gestione delle eccezioni nel blocco finale.
@mghie: Delphi ha oggetti pila assegnato:
type
TMyObject = object
private
FSomeField: PInteger;
public
constructor Init;
destructor Done; override;
end;
constructor TMyObject.Init;
begin
inherited Init;
New(FSomeField);
end;
destructor TMyObject.Done;
begin
Dispose(FSomeField);
inherited Done;
end;
var
MyObject: TMyObject;
begin
MyObject.Init;
/// ...
end;
Purtroppo, come l'esempio precedente mostra:. Stack assegnato oggetti non impediscono perdite di memoria
Quindi questo sarebbe ancora bisogno di una chiamata al distruttore in questo modo:
var
MyObject: TMyObject;
begin
MyObject.Init;
try
/// ...
finally
MyObject.Done;
end;
end;
OK, l'ammetto, questo è quasi fuori tema, ma ho pensato che potrebbe essere interessante in questo contesto in quanto oggetti di stack allocato sono stati menzionati come una soluzione (che non sono se non c'è chiamata automatica distruttore).