Las mejores prácticas para hacer anidada try / finally
-
29-08-2019 - |
Pregunta
Hola ¿Cuál es la mejor manera de hacerlo try anidado y finally en 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;
Puede sugerir una mejor manera de hacer esto?
Solución
¿qué hay de lo siguiente:
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;
Esto lo mantiene compacto, y sólo intenta liberar los casos que fueron creados. En realidad no hay necesidad de llevar a cabo la anidación ya que cualquier fallo ocasionará una caída al fin y la realización de la totalidad de la limpieza en el ejemplo que nos ha facilitado.
En lo personal yo no trato de nido dentro del mismo método ... con la excepción de un try / try / excepto / escenario finalmente. Si me encuentro que necesitan para anidar, y luego a mí que es un buen momento para pensar en la refactorización en otra llamada al método.
Editar Se ha limpiado un poco gracias a los comentarios de mghie y utku .
editar cambió la creación del objeto de no referencia de la aplicación, ya que no es necesario en este ejemplo.
Otros consejos
Me gustaría utilizar algo como esto:
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;
Para la implementación de la interfaz ver este artículo , pero se pueden crear fácilmente algo por sí mismo semejante .
EDIT:
Acabo de notar que en la Guardia artículo enlazado () es un procedimiento. En mi propio código he sobrecargado funciones de Guardia () que devuelven TObject, por encima de código de ejemplo se supone algo similar. Por supuesto, con los genéricos mucho mejor código es ahora posible ...
EDIT 2:
Si usted se pregunta por qué tratar ... finalmente es eliminado por completo en mi código: Es imposible eliminar los bloques anidados sin introducir la posibilidad de fugas de memoria (cuando los destructores plantean excepciones) o violaciónes de acceso. Por lo tanto, lo mejor es utilizar una clase de ayuda, y dejar que el recuento de referencias de interfaces de hacerse cargo por completo. La clase de ayuda puede liberar a todos los objetos que guardias, incluso si algunos de los destructores lanzar excepciones.
Hay otra variación del código sin tratar anidado ... finalmente, que me acaba de ocurrir. Si no se crea los componentes con el parámetro AOwner del constructor set a cero, entonces usted puede simplemente hacer uso de la administración de la duración que el VCL le da de forma gratuita:
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;
Soy un gran creyente en pequeño código (si no es demasiado ofuscado).
Si desea seguir este (OMI) ruta fea (grupo con el manejo de inicialización a cero para saber si se necesita liberación), que al menos debe garantizar que no se deje una excepción en una de las destructor impedir la liberación el resto de los objetos.
Algo así como:
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;
No es un buen video en href="http://codegearguru.com/video/030/TSuicide.html" de constructores y destructores
Se muestra algunos ejemplos agradables tales como:
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;
¿Qué tiene que si hay un error en el destructor de cds2
Cds1 no será destruido
editar
Otro buen recurso es:
Jim McKeeth excelente video en retardada control de excepciones en un rango de códigos III se habla acerca de los problemas en el manejo de excepciones en el bloque finally.
@mghie: Delphi tiene objetos pila asignado:
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;
Por desgracia, como muestra el ejemplo anterior:. Objetos pila asignado no evitan pérdidas de memoria
Así que esto todavía requeriría una llamada al destructor de esta manera:
var
MyObject: TMyObject;
begin
MyObject.Init;
try
/// ...
finally
MyObject.Done;
end;
end;
OK, lo admito, esto es casi fuera de tema, pero pensé que podría ser interesante en este contexto, ya que los objetos de la pila asignado se mencionaron como una solución (que no son si no hay una llamada al destructor automática).