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

Другие вопросы по тегам