Лучшая практика для выполнения вложенного оператора TRY / FINALLY
-
29-08-2019 - |
Вопрос
Привет, какой лучший способ выполнить вложенные инструкции 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;
Хорошо, я признаю это, это почти не по теме, но я подумал, что это может быть интересно в данном контексте, поскольку объекты, выделенные стеком, упоминались в качестве решения (которым они не являются, если нет автоматического вызова деструктора).