Лучшая практика для выполнения вложенных действий 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;
Можете ли вы предложить лучший способ сделать это?
Ответы
Ответ 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;
ОК, я признаю, это очень близко от темы, но я подумал, что это может быть интересно в этом контексте, поскольку упомянутые в стеке объекты были упомянуты как решение (которых они не являются, если нет автоматического вызова деструктора).