A melhor prática para fazer nested try / finally declaração
-
29-08-2019 - |
Pergunta
Oi Qual é a melhor maneira de fazer tentativa aninhada & finalmente declarações em 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;
Você pode sugerir uma maneira melhor de fazer isso?
Solução
como sobre o seguinte:
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;
Isso mantém-lo compacto, e apenas tenta libertar as instâncias que foram criadas. Não há realmente nenhuma necessidade de realizar o assentamento uma vez que qualquer falha irá resultar em queda para o final e execução de todas a limpeza no exemplo que você forneceu.
Pessoalmente eu não tentar ninho dentro do mesmo método ... com a exceção de um try / try / exceto / finally cenário. Se eu encontro-me a necessidade de ninho, então para mim que é um grande momento para pensar refatoração em outra chamada de método.
Editar Limpou um pouco graças aos comentários por mghie e utku .
Editar mudou a criação do objeto a não aplicação de referência, como não é necessário neste exemplo.
Outras dicas
Eu usaria algo como isto:
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 a implementação da interface ver este artigo , mas você pode facilmente criar algo sozinho semelhante .
EDIT:
Eu notei que no artigo ligado Guard () é um procedimento. Em meu próprio código eu sobrecarregado Guard () funções que retornam TObject, acima de código de exemplo assume algo semelhante. Claro que com os genéricos muito melhor código é agora possível ...
EDIT 2:
Se você quer saber por que tentar ... finalmente é completamente removido no meu código: É impossível para remover os blocos aninhados sem introduzir a possibilidade de vazamentos de memória (quando destruidores levantar exceções) ou violações de acesso. Portanto, é melhor usar uma classe auxiliar, e deixe a contagem de referência de interfaces assumir completamente. A classe auxiliar pode liberar todos os objetos que guardas, mesmo se alguns dos destruidores levantar exceções.
Há uma outra variação do código sem chance aninhada ... finalmente que me ocorreu. Se você não criar os componentes com o parâmetro AOwner do conjunto construtor para zero, então você pode simplesmente fazer uso da gestão de vida que o VCL dá-lhe 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;
Eu sou um grande crente em pequeno código (se não for muito ofuscado).
Se você quiser ir esta (IMO) rota feio (manipulação de grupo com inicialização a zero saber se libertação é necessário), você em garantia deve menos que você não vai deixar uma exceção em um dos destructor impedir de libertação o resto de seus objetos.
Algo 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;
Há um bom vídeo on exceções em construtores e destruidores
Ele mostra alguns exemplos interessantes, tais 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;
O que tem se há um erro no processo de destruição de cds2
Cds1 não será destruída
Editar
Outro bom recurso é:
Jim McKeeth excelente vídeo sobre Exceção Delayed Handling no intervalo de código III foram ele fala sobre problemas em lidar com exceções no bloco finally.
@mghie: Delphi ficou pilha de objetos alocados:
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;
Infelizmente, como mostra o exemplo acima:. Pilha alocada objetos não impedem vazamentos de memória
Então, isso ainda exigiria uma chamada para o processo de destruição como este:
var
MyObject: TMyObject;
begin
MyObject.Init;
try
/// ...
finally
MyObject.Done;
end;
end;
OK, eu admito, este é quase off topic, mas eu pensei que poderia ser interessante neste contexto desde que os objetos pilha alocada foram mencionados como uma solução (que não são se não há nenhuma chamada destructor automática).