Подтвердить что ты не робот

Как правильно написать 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, так извиняюсь, если это кажется прямым вопросом/ответом.

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

4b9b3361

Ответ 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 всегда уничтожается, если он назначен...