Как правильно написать Try..Finally..Except утверждений? - PullRequest
24 голосов
/ 06 июля 2011

В качестве примера возьмем следующий код:

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;

Теперь, если я не делаю что-то действительно глупое, не должно быть никаких причин иметь один и тот же код дважды в блоке "Окончание" и после, а также в блоке "Исключение".

Обычно у меня иногда есть некоторые процедуры, которые могут быть похожи на первый пример, который я опубликовал, и если я получаю ошибку, курсор застревает как песочные часы. Добавление обработчиков исключений помогает, но кажется, что это грязный способ - по сути, игнорирование блока finally, не говоря уже об уродливом коде с копированием-вставкой из частей Окончание в исключение.

Я все еще очень много изучаю Delphi, поэтому извиняюсь, если это кажется прямым вопросом / ответом.

Как должен быть правильно написан код для работы с утверждениями, правильного освобождения объектов и сбора ошибок и т. Д.?

Ответы [ 6 ]

32 голосов
/ 06 июля 2011

Вам просто нужно два 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.Однако в большинстве случаев вы должны , а не пытаться обработать исключения.Просто дайте ему распространиться до основного обработчика исключений приложения, который покажет сообщение пользователю.

Если вы обработаете исключение для уменьшения цепочки вызовов, то вызывающий код не будет знать, что вызванный им код имеетне удалось.

17 голосов
/ 06 июля 2011

Ваш оригинальный код не так плох, как вы думаете:

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 безопаснее .]

14 голосов
/ 06 июля 2011

Как объяснили другие, вам нужно защитить изменение курсора с помощью блока 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 заботится о восстановлении курсора.

3 голосов
/ 06 августа 2014

Я бы сделал это так:

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;
1 голос
/ 07 июля 2011

Сделав много кода в службах / серверах, который должен обрабатывать исключения, а не убивать приложение, я обычно обращаюсь к чему-то вроде этого:

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. Поэтому я проверяю, что объект всегда уничтожается, если ему присвоено ...

0 голосов
/ 07 июля 2011

Я думаю, что самая «правильная» версия будет такой:

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;
...