Лучшая практика для выполнения вложенного оператора TRY / FINALLY

StackOverflow https://stackoverflow.com/questions/398137

Вопрос

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

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

Это было полезно?

Решение

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

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;

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

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

Редактировать Немного почистил благодаря комментариям мгхи и utku.

Редактировать изменил создание объекта на "не ссылаться на приложение", поскольку в этом примере в этом нет необходимости.

Другие советы

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

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;

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

Редактировать:

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

ПРАВКА 2:

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

Есть еще один вариант кода без вложенной попытки ...наконец-то это только что пришло мне в голову.Если вы не создаете компоненты с параметром 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;

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

Если вы хотите пойти по этому (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;

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

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

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 не будет уничтожен

Редактировать

Еще одним хорошим ресурсом является :

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

@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;

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top