Лучшая практика для выполнения вложенного оператора 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;
Можете ли вы предложить лучший способ сделать это?
6 ответов
Как насчет следующего:
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 благодаря комментариям mghie и utku_karatas.
EDIT изменил создание объекта, чтобы не ссылаться на приложение, так как в этом примере это не обязательно.
Я бы использовал что-то вроде этого:
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 конструктора, установленным в 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;
Я большой сторонник маленького кода (если он не слишком запутанный).
Если вы хотите пойти по этому (IMO) безобразному маршруту (групповая обработка с инициализацией nil, чтобы знать, требуется ли освобождение), вы по крайней мере ДОЛЖНЫ гарантировать, что вы не допустите, чтобы исключение в одном из деструкторов препятствовало освобождению остальных ваши объекты.
Что-то вроде:
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 не будет уничтожен
РЕДАКТИРОВАТЬ
Еще один хороший ресурс:
Джим Маккит (Jim McKeeth) показал отличное видео об отложенной обработке исключений в диапазоне кода 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;
Хорошо, я признаю, что это почти не по теме, но я подумал, что это может быть интересно в этом контексте, поскольку объекты, выделенные из стека, упоминались как решение (а это не так, если нет автоматического вызова деструктора).