Лучшая практика для выполнения вложенного оператора TRY / FINALLY - PullRequest
20 голосов
/ 29 декабря 2008

Привет Каков наилучший способ делать вложенные операторы try & finally в 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;

Можете ли вы предложить лучший способ сделать это?

Ответы [ 6 ]

30 голосов
/ 29 декабря 2008

как насчет следующего:

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;

Это делает его компактным, и только пытается освободить экземпляры, которые были созданы. В действительности нет необходимости выполнять вложение, поскольку ЛЮБОЙ сбой приведет к переходу в окончание и выполнению всей очистки в приведенном вами примере.

Лично я стараюсь не вкладывать в один и тот же метод ... за исключением того, что это сценарий "попытка / попытка / исключение / окончание". Если я чувствую, что нуждаюсь в вложении, тогда для меня это прекрасное время, чтобы подумать о рефакторинге в вызов другого метода.

РЕДАКТИРОВАТЬ Немного исправлено благодаря комментариям mghie и utku .

РЕДАКТИРОВАТЬ изменил создание объекта, чтобы не ссылаться на приложение, как это не требуется в этом примере.

18 голосов
/ 29 декабря 2008

Я бы использовал что-то вроде этого:

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;

Для реализации интерфейса см. эту статью, но вы можете легко создать нечто подобное самостоятельно.

EDIT:

Я только что заметил, что в связанной статье Guard () - это процедура. В моем собственном коде я перегружен функциями Guard (), которые возвращают TObject, приведенный выше пример кода предполагает нечто подобное. Конечно, с дженериками гораздо лучший код теперь возможен ...

РЕДАКТИРОВАТЬ 2:

Если вы удивляетесь, зачем пытаться ... наконец-то полностью удалено из моего кода: невозможно удалить вложенные блоки без возможности утечки памяти (когда деструкторы вызывают исключения) или нарушений доступа. Поэтому лучше использовать вспомогательный класс и позволить подсчету ссылок интерфейсов полностью занять место. Вспомогательный класс может освободить все охраняемые им объекты, даже если некоторые из деструкторов вызывают исключения.

4 голосов
/ 30 декабря 2008

Есть еще один вариант кода без вложенной попытки ... наконец, это только что пришло мне в голову. Если вы не создаете компоненты с параметром AOwner конструктора, установленным в ноль, то вы можете просто использовать управление временем жизни, которое VCL предоставляет вам бесплатно:

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;

Я большой сторонник маленького кода (если он не слишком запутан).

3 голосов
/ 23 марта 2009

Если вы хотите пойти по этому (IMO) безобразному маршруту (групповая обработка с инициализацией на ноль, чтобы знать, требуется ли освобождение), вы, по крайней мере, ДОЛЖНЫ гарантировать, что вы не позволите исключению в одном из деструкторов предотвратить освобождение остальные ваши объекты.
Что-то вроде:

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;
2 голосов
/ 10 января 2009

Хорошее видео о исключениях в конструкторах и деструкторах

Он показывает несколько хороших примеров, таких как:

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;

Что если в деструкторе cds2

есть ошибка

Cds1 не будет уничтожен

РЕДАКТИРОВАТЬ

Еще один хороший ресурс:

Джим Маккит превосходное видео о Отложенная обработка исключений в диапазоне кода III, где он рассказывает о проблемах обработки исключений в блоке finally.

1 голос
/ 29 декабря 2008

@ mghie: Delphi получил объекты, выделенные из стека:

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;

К сожалению, как показывает приведенный выше пример: объекты, размещенные в стеке, не предотвращают утечки памяти.

Так что для этого все равно потребуется вызов деструктора, подобный этому:

var
  MyObject: TMyObject;

begin
  MyObject.Init;
  try
    /// ...
  finally
    MyObject.Done;
  end;
end;

Хорошо, я признаю, что это почти не по теме, но я подумал, что это может быть интересно в этом контексте, поскольку объекты, выделенные из стека, упоминались как решение (а это не так, если нет автоматического вызова деструктора).

...