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?

Foi útil?

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).

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top