Подтвердить что ты не робот

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

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

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

4b9b3361

Ответ 1

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

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;

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

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

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

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

Ответ 2

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

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:

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

Ответ 3

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

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

Ответ 4

Если вы хотите использовать этот (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;

Ответ 5

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

Здесь показаны некоторые приятные примеры, например:

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.

Ответ 6

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

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

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

var
  MyObject: TMyObject;

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

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