Как правильно написать Try..Finally.. За исключением утверждений?
В качестве примера возьмите следующий код:
procedure TForm1.Button1Click(Sender: TObject);
var
Obj: TSomeObject;
begin
Screen.Cursor:= crHourGlass;
Obj:= TSomeObject.Create;
try
// do something
finally
Obj.Free;
end;
Screen.Cursor:= crDefault;
end;
если в разделе // do something
произошла ошибка, созданный объект TSomeObject не будет освобожден, а Screen.Cursor все равно будет зацикливаться на часовом стекле, потому что код был сломан, прежде чем попасть в те линии?
Теперь, если я не ошибаюсь, должен быть установлен оператор Exception для решения любого такого случая ошибки, например:
procedure TForm1.Button1Click(Sender: TObject);
var
Obj: TSomeObject;
begin
try
Screen.Cursor:= crHourGlass;
Obj:= TSomeObject.Create;
try
// do something
finally
Obj.Free;
end;
Screen.Cursor:= crDefault;
except on E: Exception do
begin
Obj.Free;
Screen.Cursor:= crDefault;
ShowMessage('There was an error: ' + E.Message);
end;
end;
Теперь, если я не делаю что-то действительно глупое, не должно быть причин иметь один и тот же код дважды в блоке finally и после него и в блоке Exception.
В основном у меня иногда есть некоторые процедуры, которые могут быть похожими на первый отправленный мной образец, и если я получу ошибку, курсор застрянет как часовое стекло. Добавление обработчиков исключений помогает, но, похоже, это грязный способ сделать это - в основном игнорируя блок finally, не говоря уже о уродливом коде с копией-парой из частей от Expression to Exception.
Я все еще очень изучаю Delphi, так извиняюсь, если это кажется прямым вопросом/ответом.
Каким образом код должен быть правильно написан для обработки заявлений и правильного освобождения объектов и ошибок захвата и т.д.?
Ответы
Ответ 1
Вам просто нужны два блока try/finally
:
Screen.Cursor:= crHourGlass;
try
Obj:= TSomeObject.Create;
try
// do something
finally
Obj.Free;
end;
finally
Screen.Cursor:= crDefault;
end;
Следующее правило - использовать finally
вместо except
для защиты ресурсов. Как вы заметили, если вы попытаетесь сделать это с помощью except
, вам придется дважды писать код завершения.
Как только вы введете блок try/finally
, гарантируется выполнение кода в разделе finally
, независимо от того, что происходит между try
и finally
.
Таким образом, в приведенном выше коде внешний try/finally
гарантирует, что Screen.Cursor
восстанавливается перед любыми исключениями. Аналогично внутренний try/finally
гарантирует, что Obj
будет уничтожен в случае возникновения каких-либо исключений в течение его срока службы.
Если вы хотите обработать исключение, вам нужен отдельный блок try/except
. Однако в большинстве случаев вы должны не пытаться обрабатывать исключения. Просто позвольте ему распространяться до главного обработчика исключений приложения, который покажет пользователю сообщение.
Если вы обрабатываете исключение, чтобы снизить цепочку вызовов, тогда вызывающий код не будет знать, что вызванный им код не удался.
Ответ 2
Ваш исходный код не так плох, как вы думаете (хотя и плох):
procedure TForm1.Button1Click(Sender: TObject);
var
Obj: TSomeObject;
begin
Screen.Cursor := crHourGlass;
Obj := TSomeObject.Create;
try
// do something
finally
Obj.Free;
end;
Screen.Cursor := crDefault;
end;
Obj.Free
будет выполняться независимо от того, что произойдет, когда вы // do something
. Даже если возникает исключение (после try
), блок finally
будет выполнен! В этом весь смысл конструкции try..finally
!
Но вы также хотите восстановить курсор. Лучший способ - использовать две конструкции try..finally
:
procedure TForm1.Button1Click(Sender: TObject);
var
Obj: TSomeObject;
begin
Screen.Cursor := crHourGlass;
try
Obj := TSomeObject.Create;
try
// do something
finally
Obj.Free;
end;
finally
Screen.Cursor := crDefault;
end;
end;
Ответ 3
Как объяснили другие, вам необходимо защитить изменение курсора с помощью блока try finally
. Чтобы не писать, я использую такой код:
unit autoCursor;
interface
uses Controls;
type
ICursor = interface(IInterface)
['{F5B4EB9C-6B74-42A3-B3DC-5068CCCBDA7A}']
end;
function __SetCursor(const aCursor: TCursor): ICursor;
implementation
uses Forms;
type
TAutoCursor = class(TInterfacedObject, ICursor)
private
FCursor: TCursor;
public
constructor Create(const aCursor: TCursor);
destructor Destroy; override;
end;
{ TAutoCursor }
constructor TAutoCursor.Create(const aCursor: TCursor);
begin
inherited Create;
FCursor := Screen.Cursor;
Screen.Cursor := aCursor;
end;
destructor TAutoCursor.Destroy;
begin
Screen.Cursor := FCursor;
inherited;
end;
function __SetCursor(const aCursor: TCursor): ICursor;
begin
Result := TAutoCursor.Create(aCursor);
end;
end.
Теперь вы просто используете его как
uses
autoCursor;
procedure TForm1.Button1Click(Sender: TObject);
var
Obj: TSomeObject;
begin
__SetCursor(crHourGlass);
Obj:= TSomeObject.Create;
try
// do something
finally
Obj.Free;
end;
end;
и механизм интерфейса подсчета ссылок Delphi позаботится о восстановлении курсора.
Ответ 4
Я бы сделал это следующим образом:
var
savedCursor: TCursor;
Obj: TSomeObject;
begin
savedCursor := Screen.Cursor;
Screen.Cursor := crHourGlass;
Obj:= TSomeObject.Create;
try
try
// do something
except
// record the exception
end;
finally
if Assigned(Obj) then
Obj.Free;
Screen.Cursor := savedCursor;
end;
end;
Ответ 5
Я думаю, что самая "правильная" версия будет такой:
procedure TForm1.Button1Click(Sender: TObject);
var
Obj: TSomeObject;
begin
Obj := NIL;
Screen.Cursor := crHourGlass;
try
Obj := TSomeObject.Create;
// do something
finally
Screen.Cursor := crDefault;
Obj.Free;
end;
end;
Ответ 6
Сделав много кода в службах/серверах, которые должны обрабатывать исключения и не убивать приложение, я обычно делаю что-то вроде этого:
procedure TForm1.Button1Click(Sender: TObject);
var
Obj: TSomeObject;
begin
try
Obj := NIL;
try
Screen.Cursor := crHourGlass;
Obj := TSomeObject.Create;
// do something
finally
Screen.Cursor := crDefault;
if assigned(Obj) then FreeAndNil(Obj);
end;
except
On E: Exception do ; // Log the exception
end;
end;
Обратите внимание на попытку в конце; внутри try кроме; и размещение объекта Obj.
если Obj создает другие вещи внутри конструктора, он может работать на полпути и терпеть неудачу с исключением внутри .create(); но все же быть созданным Obj. Поэтому я убеждаюсь, что Obj всегда уничтожается, если он назначен...