delphitry-finallytry-except

How to correctly write Try..Finally..Except statements?


Take the following code as a sample:

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;

if there was an error happening in the // do something section, the TSomeObject that was created I assume will not be freed and the Screen.Cursor will still be stuck as an Hour Glass, because the code was broke before getting to those lines?

Now unless I am mistaking, an Exception statement should be in place to deal with any such occurence of an error, something like:

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;

Now unless I am doing something really stupid, there should be no reason to have the same code twice in the Finally block and after, and in the Exception block.

Basically I sometimes have some procedures that may be similar to the first sample I posted, and if I get an error the cursor is stuck as an Hour Glass. Adding the Exception handlers help, but it seems a dirty way of doing it - its basically ignoring the Finally block, not to mention ugly code with copy-paste from the Finally to Exception parts.

I am still very much learning Delphi so apologies if this appears to be a straight forward question/answer.

How should the code be correctly written to deal with the Statements and correctly freeing objects and capturing errors etc?


Solution

  • You just need two try/finally blocks:

    Screen.Cursor:= crHourGlass;
    try
      Obj:= TSomeObject.Create;
      try
        // do something
      finally
        Obj.Free;
      end;
    finally
      Screen.Cursor:= crDefault;
    end;
    

    The guideline to follow is that you should use finally rather than except for protecting resources. As you have observed, if you attempt to do it with except then you are forced to write the finalising code twice.

    Once you enter the try/finally block, the code in the finally section is guaranteed to run, no matter what happens between try and finally.

    So, in the code above, the outer try/finally ensures that Screen.Cursor is restored in the face of any exceptions. Likewise the inner try/finally ensures that Obj is destroyed in case of any exceptions being raised during its lifetime.


    If you want to handle an exception then you need a distinct try/except block. However, in most cases you should not attempt to handle exceptions. Just let it propagate up to the main application exception handler which will show a message to the user.

    If you handle the exception to low down the call chain then the calling code will not know that the code it called has failed.