Как правильно писать инструкции 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, потому что код был сломан до того, как добраться до этих строк?

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

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

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


person Community    schedule 06.07.2011    source источник
comment
Объясните, почему вы добавили в этот код блок try / finally? Возможно, Андреас прав, полагая, что за вопросом стоит понимание того, что означает попытка / наконец.   -  person David Heffernan    schedule 06.07.2011
comment
не уверен, что вы имеете в виду, но я добавил попытку .. наконец, чтобы убедиться, что TSomeObject высвобождается из памяти после того, как он сделал то, что нужно сделать   -  person    schedule 08.07.2011


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

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

person David Heffernan    schedule 06.07.2011
comment
+ в большинстве случаев вам не следует пытаться обрабатывать исключения. - person Cosmin Prund; 07.07.2011
comment
Красиво объяснено! В: Можем ли мы предсказать, когда будет выполнен блок finally? В этой статье говорится, что при исключении выполнение переходит к последней части. До сих пор я знал, что блок finally всегда выполняется, но без гарантии когда он выполняется. К тому же, если он действительно перескочил на finally при исключении, разве except и finally не будут вести себя одинаково, допуская их взаимозаменяемые использования? Кстати, последний вопрос не к вам сам по себе, а к автору этой статьи, но вы более чем можете объяснить. TIA. - person Sнаđошƒаӽ; 01.04.2019
comment
@ Sнаđошƒаӽ Блок finally выполняется вне зависимости от того, есть ли исключение, поэтому он не взаимозаменяем с except. А что касается того, когда он выполняется, то он выполняется после того, как все в блоке try выполнено. Обратите внимание, что блок try может выйти раньше из-за Exit, Break, Continue или повышения. - person David Heffernan; 01.04.2019
comment
Спасибо, но моя основная проблема не получила ответа, и я уверен, что это потому, что я недостаточно ясно дал понять. Дай мне попробовать снова. В случае возникновения исключения в блоке try-finally, переходит ли выполнение к блоку finally, как только возникает исключение (как упоминалось в статье, которую я цитировал ранее)? TIA. И я знаю, что except и finally не взаимозаменяемы; Я упомянул об этом только для того, чтобы укрепить свою позицию против идеи в той статье;) - person Sнаđошƒаӽ; 01.04.2019
comment
@ Sнаđошƒаӽ Все сложнее. Исключение может возникать в функциях, вызываемых в блоке try, или в функциях, вызываемых этими функциями, и так далее. Там могут быть другие обработчики исключений, которые выполняются до того, как здесь будет достигнут блок finally. - person David Heffernan; 01.04.2019

Ваш исходный код не совсем так плох, как вы думаете (хотя и плохой):

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;
person Andreas Rejbrand    schedule 06.07.2011
comment
@ Дэвид, это моя проблема, когда возникает ошибка, Курсор застревает как песочные часы - person ; 06.07.2011
comment
@Andreas Я процитирую тебя. Ты никогда не узнаешь. Это похоже на пристегнутые ремни безопасности. В большинстве случаев в этом нет необходимости, но оно того стоит, потому что всегда есть риск аварии. - person David Heffernan; 06.07.2011
comment
Я использую это все время, чтобы «вернуть» курсор в нормальное состояние. +1 - person Z80; 07.07.2011
comment
Нет нужды в двойной попытке ... наконец, Андреас. Просто поменяйте порядок в конце - Obj всегда будет освобожден, все плохое, что может случиться, - это то, что курсор не изменится. - person Fabricio Araujo; 08.07.2011
comment
@Fabricio: Думаю, тогда нам также следует поменять порядок перед try. Но если курсор остается crHourGlass, это досадная ошибка, не так ли? А написать "уютный" try..finally - не так уж и много ... - person Andreas Rejbrand; 08.07.2011
comment
@Andreas: Но это будет видимая ошибка - так что кому-то нужно будет только отследить, где произошло последнее изменение курсора. Утечки памяти гораздо больше раздражают, когда нужно выяснить, что произошло, поскольку они являются тихими ошибками (не исключение). - person Fabricio Araujo; 11.07.2011
comment
@Fabricio: Правильный способ - использовать два try..finally. (То есть я возражаю против вашего первоначального комментария.) - person Andreas Rejbrand; 11.07.2011
comment
Если конструктор TSomeObject дает сбой, состояние курсора менее актуально. У вашей программы более глубокая проблема. В этот момент вас не будет заботить курсор. Из-за этого второй блок кода, показанный Андреасом, отлично подходит. - person Z80; 30.09.2019

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

person ain    schedule 06.07.2011
comment
Очень умно, возможно, слишком умно. Я бы не стал защищать это, особенно для кого-то относительно нового, кто попробует / наконец и попробует / кроме. И если вы должны это сделать, зачем возиться с ICursor. Просто заставьте __SetCursor вернуть IInterface. - person David Heffernan; 06.07.2011
comment
Почему бы и нет, ничего опасного там нет. Компилятор просто создает для вас невидимый try finally блок ... ICursor предназначен для обеспечения дополнительных функций (не включенных в пример), то есть свойства читать исходный курсор, который необходимо восстановить. - person ain; 06.07.2011
comment
О, он отлично работает. Это просто еще один уровень сложности. - person David Heffernan; 06.07.2011
comment
Очень умно, но Delphi просто добавляет невидимый try finally за кулисами, чтобы убедиться, что счетчик ссылок достигает нуля, поэтому вы просто добавляете сложность, чтобы обезопасить несколько нажатий клавиш, заставляя компилятор генерировать по существу тот же код, что и дополнительная попытка - наконец себя. Я не вижу здесь пользы. Похоже на конкурс обфустации кода. ИМО, явная попытка, наконец, намного лучше, потому что она лучше объясняет намерение кода, и код написан для человеческого потребления, а не для машины. - person Johan; 07.07.2011
comment
Я не считаю это добавлением сложности; Преимущество состоит в том, что у вас нет нескольких (видимых) вложенных блоков try finally, которые затрудняют чтение кода. - person ain; 07.07.2011
comment
-1 запутанный код. Когда вы что-то пишете, подумайте, кто это прочитает следующим. Не только код, кстати. - person Sam; 14.07.2011
comment
Это разумно, но не рекомендуется. Не усложняйте чтение кода без реальной пользы. Будь проще. - person kling; 02.12.2014
comment
Эта концепция известна как RAII и очень часто используется в C ++. Это гарантирует освобождение ресурсов и делает любые вложенные блоки try-finally (которые, на мой взгляд, еще больше запутывали код) устаревшими. См. en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization - person niks; 14.05.2018
comment
Действительно умно. Но я по возможности избегаю интерфейсов. +1 в любом случае за красивую теоретическую идею :) - person Z80; 30.09.2019

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

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;
person dummzeuch    schedule 07.07.2011
comment
Obj не освобождается, если Screen.Cursor := crDefault вызывает исключение. - person David Heffernan; 07.07.2011
comment
Согласованный. Самая правильная версия, безусловно, с двумя try..finally. - person Andreas Rejbrand; 07.07.2011
comment
если Screen.Cursor: = crDefault вызывает исключение - - - Полностью согласен с вами, но может ли это действительно случиться? Каковы шансы в реальном мире ?? - person Z80; 07.07.2011
comment
@david: Я должен согласиться. Если я переключу порядок операторов в блоке finally, Screen.Cursor не будет изменен обратно, если Obj.Free вызовет исключение. Это оставляет две конструкции try..finally как единственно правильное решение, как уже сказал Андреас. Обычно я предполагаю, что назначение Screen.Cursor не может завершиться ошибкой, но это может быть неправильно. - person dummzeuch; 09.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;

Обратите внимание на попытку, наконец; внутри try except; и размещение создания Obj.

если Obj создает другие вещи внутри своего конструктора, он может работать наполовину и завершиться ошибкой с исключением внутри .create (); но все же быть созданным Obj. Поэтому я убеждаюсь, что Obj всегда уничтожается, если он был назначен ...

person K.Sandell    schedule 07.07.2011
comment
Obj будет протекать, если Screen.Cursor := crDefault повысится. Кроме того, что с if assigned(Obj) then FreeAndNil(Obj)? Вызовы TObject.Free уже включают if assigned(...) тест. - person David Heffernan; 07.07.2011
comment
Кроме того, если TSomeObject.Create вызывает исключение, его деструктор будет вызван автоматически (проверьте это!). Obj вообще не будет назначен в этом случае, потому что исключение произошло до назначения, поэтому нет необходимости освобождать его. Итак, Obj: = TSomeObj.Create или Screen.Cursor может быть выполнено вне try..inally. - person dummzeuch; 09.07.2011
comment
«Если назначено (...)» на самом деле унаследовано от старых времен ... и я изменю свой путь! - Не понимал, что исключение в create () всегда будет вызывать уничтожение .. спасибо! - person K.Sandell; 12.07.2011

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

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;
person sacconago    schedule 06.08.2014
comment
Где объявлен Obj? В исходном вопросе это локальная переменная. Если Obj был назначен ранее, и этот код назначает новый экземпляр Obj, могут произойти непредвиденные вещи. - person mjn; 06.08.2014
comment
Не делай этого. (1) Если конструктор TSomeObject.Create не работает, вы застряли в курсоре в виде песочных часов, что действительно ухудшает внешний вид вашего приложения. (2) Вам никогда не нужно писать if Assigned(X) then X.Free, потому что X.Free в основном делает if Assigned(X) then X.Destroy, так что в основном вы делаете if Assigned then if Assigned then Destroy. (3) Если деструктор не работает (чего не должно быть, но это возможно), вы застряли с курсором в виде песочных часов. Просто придерживайтесь стандартной идиомы использования двух try..finally блоков! - person Andreas Rejbrand; 30.09.2019

Если вы нашли здесь свой путь и искали, как создать конструкцию try-except-finally из C # в 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, хотя первое будет более распространенным.

person AlainD    schedule 18.07.2021