Как правильно написать Try..Finally..Except утверждений?
Возьмите следующий код в качестве примера:
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 все равно будет зависать как Hour Glass, потому что перед тем, как добраться до этих строк, код был взломан?
Теперь, если я не ошибаюсь, должно существовать заявление об исключении, чтобы иметь дело с любым таким случаем ошибки, что-то вроде:
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;
Теперь, если я не делаю что-то действительно глупое, не должно быть оснований иметь один и тот же код дважды в блоке "После" и после, а также в блоке "Исключение".
Обычно у меня иногда есть некоторые процедуры, которые могут быть похожи на первый пример, который я опубликовал, и если я получаю ошибку, курсор застревает как Hour Glass. Добавление обработчиков исключений помогает, но кажется, что это грязный способ - по сути, игнорирование блока finally, не говоря уже об уродливом коде с копированием-вставкой из частей "Окончание в исключение".
Я все еще очень много изучаю Delphi, поэтому извиняюсь, если это кажется прямым вопросом / ответом.
Как должен быть правильно написан код для работы с утверждениями и правильного освобождения объектов, сбора ошибок и т. Д.?
7 ответов
Вам просто нужно два 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
блок. Однако в большинстве случаев вы не должны пытаться обрабатывать исключения. Просто дайте ему распространиться до основного обработчика исключений приложения, который покажет сообщение пользователю.
Если вы обработаете исключение, чтобы уменьшить цепочку вызовов, то вызывающий код не будет знать, что вызванный код потерпел неудачу.
Ваш оригинальный код не так плох, как вы думаете:
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;
[Впрочем, я бы тоже не против
procedure TForm1.Button1Click(Sender: TObject);
var
Obj: TSomeObject;
begin
Obj := TSomeObject.Create;
Screen.Cursor := crHourGlass;
try
// do something
finally
Screen.Cursor := crDefault;
Obj.Free;
end;
end;
перебор. Риск Screen.Cursor := crHourGlass
сбой довольно низкий, но в этом случае объект не будет освобожден (finally
не будет работать, потому что вы не внутри try
), поэтому двойной try..finally
безопаснее.]
Как объяснили другие, вам нужно защитить изменение курсора с помощью 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 заботится о восстановлении курсора.
Я думаю, что самая "правильная" версия будет такой:
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;
Если вы нашли свой путь сюда и искали, как сделать
try-except-finally
построить из С# в Delphi:
// C#
try
{
// Do something
}
catch
{
// Exception!
}
finally
{
// Always do this...
}
Ответ заключается в том, что вы не можете сделать это напрямую. Вместо этого, как намекает @sacconago, вложите
try
блоки следующим образом:
// Delphi
try
try
// Do something
except
// Exception!
end;
finally
// Always do this...
end;
Одна приятная особенность Delphi заключается в том, что вы можете вкладывать блоки как
try...except...finally
или же
try...finally...except
, хотя первое было бы более распространенным.
Я бы сделал это так:
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;
Сделав много кода в службах / серверах, который должен обрабатывать исключения и не убивать приложение, я обычно делаю что-то вроде этого:
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;
Обратите внимание на попытку наконец; внутри попробовать, кроме; и размещение создания Obj.
если Obj создает другие вещи внутри своего конструктора, он может работать на полпути и завершиться неудачей с исключением внутри.create(); но все же быть созданным Obj. Поэтому я удостоверяюсь, что Обь всегда уничтожается, если ему назначено...