如何正确编写Try..Finally..Except语句?

26 delphi try-finally 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;
Run Code Online (Sandbox Code Playgroud)

如果在该// do something部分中发生错误,我创建的TSomeObject将不会被释放,并且Screen.Cursor仍然会被卡在一个小时玻璃中,因为代码在进入这些行之前已经被破坏了?

现在除非我误会,否则应该有一个异常声明来处理任何错误的发生,例如:

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;
Run Code Online (Sandbox Code Playgroud)

现在除非我做了一些非常愚蠢的事情,否则应该没有理由在Finally块和之后以及在Exception块中两次使用相同的代码.

基本上我有时会有一些程序可能类似于我发布的第一个样本,如果我收到错误,光标会被卡在小时玻璃上.添加Exception处理程序会有所帮助,但它似乎是一种肮脏的方式 - 它基本上忽略了Finally块,更不用说从Finally到Exception部分的复制粘贴的丑陋代码.

如果这似乎是一个直截了当的问题/答案,我仍然非常学习德尔福.

如何正确编写代码来处理语句并正确释放对象和捕获错误等?

Dav*_*nan 34

你只需要两个try/finally街区:

Screen.Cursor:= crHourGlass;
try
  Obj:= TSomeObject.Create;
  try
    // do something
  finally
    Obj.Free;
  end;
finally
  Screen.Cursor:= crDefault;
end;
Run Code Online (Sandbox Code Playgroud)

遵循的准则是您应该使用finally而不是except保护资源.正如您所观察到的,如果您尝试使用它,except那么您将被迫编写两次最终化代码.

一旦你进入try/finally块,finally无论在try和之间发生什么,保证该部分中的代码都会运行finally.

因此,在上面的代码中,外部try/finally确保Screen.Cursor在面对任何异常时恢复.同样,内部try/finally确保Obj在其生命周期中出现任何异常时被销毁.


如果要处理异常,则需要一个不同的try/except块.然而,在大多数情况下,你应该尝试处理异常.只是让它传播到主应用程序异常处理程序,它将向用户显示一条消息.

如果你处理异常以降低调用链,那么调用代码将不知道它调用的代码是否失败.

  • +"对于"在大多数情况下你应该**不要**试图处理异常." (14认同)
  • @Sнаđошƒаӽ更复杂。可以在try块中调用的函数或实际上由这些函数调用的函数等等中引发异常。可能还有其他异常处理程序在到达此处的finally块之前执行。 (2认同)

And*_*and 17

您的原始代码没有您想象的那么糟糕:

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;
Run Code Online (Sandbox Code Playgroud)

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;
Run Code Online (Sandbox Code Playgroud)

[但是,我也不介意

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;
Run Code Online (Sandbox Code Playgroud)

太多了.Obj.Free失败的风险非常低,但在这种情况下,对象不会被释放(// do something因为你不在里面而不会运行try),所以双重finally更安全.

  • @Andreas我要引用你的话."你永远不会知道.它就像戴着安全带.大多数时候,这显然是不必要的,但这是值得的,因为总有发生事故的风险." (3认同)

ain*_*ain 15

正如其他人所解释的那样,您需要使用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.
Run Code Online (Sandbox Code Playgroud)

现在你就像使用它一样

uses
   autoCursor;

procedure TForm1.Button1Click(Sender: TObject);
var
  Obj: TSomeObject;
begin
  __SetCursor(crHourGlass);

  Obj:= TSomeObject.Create;
  try
    // do something
  finally
    Obj.Free;
  end;
end;
Run Code Online (Sandbox Code Playgroud)

和Delphi的引用计数接口机制负责恢复游标.

  • 我不认为它增加了复杂性; 好处是你没有多个(可见的)嵌套try finally块,这使得代码更难以阅读. (10认同)
  • 非常聪明,但是Delphi只是在幕后添加了一个看不见的`try finally`以确保引用计数变为零,所以你只是增加复杂性以安全地进行几次击键,同时迫使编译器生成与执行的基本相同的代码.额外尝试 - 最后自己.我没有看到这里的好处它看起来像一个代码obfustification竞赛.IMO显式尝试最终更好,因为它更好地解释了代码的意图,代码是为人类消费编写的,而不是为机器编写的. (4认同)
  • 这个概念称为 RAII,在 C++ 中非常常用。它保证释放资源,并使任何嵌套的 try-finally 块(在我看来,这更加混淆了代码)变得过时。请参阅 https://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization (2认同)

dum*_*uch 6

我认为最“正确”的版本是这样的:

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;
Run Code Online (Sandbox Code Playgroud)

  • 如果 `Screen.Cursor := crDefault` 引发异常,则不会释放 `Obj`。 (3认同)
  • @大卫:我必须同意。如果我切换finally块中的语句顺序,如果Obj.Free引发异常,Screen.Cursor将不会改回来。正如安德烈亚斯已经说过的那样,这使得两个 try..finally 构造成为唯一正确的解决方案。我通常认为 Screen.Cursor 分配不会失败,但这可能是错误的。 (2认同)