The Wiert Corner – irregular stream of stuff

Jeroen W. Pluimers on .NET, C#, Delphi, databases, and personal interests

  • My work

  • My badges

  • Twitter Updates

  • My Flickr Stream

  • Pages

  • All categories

  • Enter your email address to subscribe to this blog and receive notifications of new posts by email.

    Join 1,798 other followers

Delphi: a memento that executes any code at end of method

Posted by jpluimers on 2014/08/07

Following up on yesterdays Delphi: using IInterface to restore cursor at end of mehod (prelude to a memento that executes any code at end of method) here is the memento I meant.

They are based on anonymous methods, which in Delphi are closures: they capture location.

The location is kept just as long as needed, based on a well known Delphi reference counting mechanism: interfaces. The same one I used for the TTemporaryCursor class (and one of the reasons the TTemporaryCursor will keep functioning).

My goal was to simplify code like this:

procedure TTemporaryCursorMainForm.TemporaryCursorClassicButtonClick(Sender: TObject);
var
  Button: TButton;
  SavedCursor: TCursor;
  SavedEnabled: Boolean;
begin
  Button := Sender as TButton;
  SavedEnabled := Button.Enabled;
  try
    Button.Enabled := False;
    SavedCursor := Screen.Cursor;
    try
      Screen.Cursor := crHourGlass;
      Sleep(3000);
    finally
      Screen.Cursor := SavedCursor;
    end;
  finally
    Button.Enabled := SavedEnabled;
  end;
end;

Into this:

procedure TTemporaryCursorMainForm.TemporaryCursorMementoButtonClick(Sender: TObject);
var
  Button: TButton;
  SavedEnabled: Boolean;
begin
  TTemporaryCursor.SetTemporaryCursor();
  Button := Sender as TButton;
  SavedEnabled := Button.Enabled;
  TAnonymousMethodMemento.CreateMemento(procedure begin Button.Enabled := SavedEnabled; end);
  Button.Enabled := False;
  Sleep(3000); // sleep 3 seconds with the button disabled crHourGlass cursor
  // Delphi will automatically restore the cursor
end;

We’ve already seen one of the try…finally…end blocks vanish by using TTemporaryCursor. Now lets look at TAnonymousMethodMemento:

unit AnonymousMethodMementoUnit;

interface

uses
  System.SysUtils;

type
  IAnonymousMethodMemento = interface(IInterface)
  ['{29690E1E-24C8-43A5-8FDF-5F21BB32CEC2}']
  end;

  TAnonymousMethodMemento = class(TInterfacedObject, IAnonymousMethodMemento)
  strict private
    FFinallyProc: TProc;
  public
    constructor Create(const AFinallyProc: TProc);
    destructor Destroy; override;
    procedure Restore(const AFinallyProc: TProc); virtual;
    class function CreateMemento(const AFinallyProc: TProc): IAnonymousMethodMemento;
  end;

implementation

{ TAnonymousMethodMemento }
constructor TAnonymousMethodMemento.Create(const AFinallyProc: TProc);
begin
  inherited Create();
  FFinallyProc := AFinallyProc;
end;

destructor TAnonymousMethodMemento.Destroy;
begin
  Restore(FFinallyProc);
  inherited Destroy();
end;

class function TAnonymousMethodMemento.CreateMemento(const AFinallyProc: TProc): IAnonymousMethodMemento;
begin
  Result := TAnonymousMethodMemento.Create(AFinallyProc);
end;

procedure TAnonymousMethodMemento.Restore(const AFinallyProc: TProc);
begin
  AFinallyProc();
end;

end.

Like TTemporaryCursor, I’ve kept it self-contained.

It uses a TProc parameter – a parameterless anonymous method – called AFinallyProc that needs to be executed right before the memento goes out of scope.

It can be called like any method, as to the compiler it is a method.

–jeroen

11 Responses to “Delphi: a memento that executes any code at end of method”

  1. […] has a deterministic way of coping with interfaces (hence you can do a One-liner RAII in Delphi, or make a memento): Interface references are released at the end of their […]

  2. […] restore cursor at end of mehod (prelude to a memento that executes any code at end of method) and Delphi: a memento that executes any code at end of method: his TScopeExitNotifier is virtually the same as […]

  3. eric said

    An issue with this approach (besides those mentioned) is that there will be no UI feedback, and you’ll get an “application not responding” Windows warning, unless… you have some form of Application.ProcessMessages involved, but that opens another whole can of worms.

  4. David Heffernan said

    I loath the approach encapsulated by TAnonymousMethodMemento. To me it makes matters worse. You still have a local variable to declare. The intent of the code is now hidden away. And you rely on undocumented implementation detail. Who says the interface lasts until the end of the function? Who says which order the interfaces are finalised. How do you interleave this implicit finalisation with explicit finalisation? It may be clever, but it’s not an improvement. Try finally wins here.

    • Moritz Beutel said

      A local variable is not required. The temporary interface returned by the redundantly named TTemporaryCursor.SetTemporaryCursor() is kept alive until the end of the scope.

      This technique has been described before by Barry Kelly in 2010:
      http://blog.barrkel.com/2010/01/one-liner-raii-in-delphi.html

      In real code I also prefer try/finally because I find it more comprehensible.

      • jpluimers said

        Thanks for another reference. I knew Barry Blogged about it, just forgot when (:

        I wish Delphi had a `using` like construct. That would make scoping more clear as well.

      • David Heffernan said

        There is a local variable to store the original value. And the temporary interface is kept alive how long? Where is that documented? And what order are they finalised? All undocumented. Opaque to the code reader.

  5. Jon Lennart Aasenden said

    What would be cool was:

    Procedure TMyObject.DoSomething;
    Entry()
    Begin
    End;

    Exit()
    Begin
    End;

    Begin
    //Code here
    End;

    That way we could do contract coding, validation in head, cleanup in tail :)

  6. Jon Lennart Aasenden said

    Hm. Interesting. But werent actions created to avoid all this? I know a lot of coders still code states manually, but I personally have used actions since Delphi 7 i think – and it takes care of everything regarding states. But a “execute once this proc is done” feature would be very welcome. I just coded that for javascript – and it will be most used from now on :)

    • jpluimers said

      I love actions for design time stuff (forms/frames/datamodules) but for pure coding, these interface tricks work pretty well.

      It would be nice to have pre and post conditions. Other Delphi like languages have a nice approach in that area (:

      • Joseph G. Mitzen said

        >Other Delphi like languages have a nice approach in that area (:

        In Python you have a very interesting keyword called “with” which is nothing like the Delphi version. It’s a “context manager” that guarantees that initialization/clean-up code gets run even if an exception is raised. It executes an object’s “__enter__” and “__exit__” methods if it has them. For instance, if you’re opening a file you can do:

        with open(‘c:\whatever.txt’) as infile:
        ….

        and when it exits the block it calls the __exit__ method of infile, which closes the file for you. :-) People have come up with a lot of nice ways to use it that I’ve seen so far… automatically beginning and ending transactions for SQL queries, closing database cursors, even trapping exceptions that one wants to ignore, such as ignoring a file not found exception when deleting a file.

        Once I got over the different functionality, I realized it was more useful/less dangerous than Delphi’s “with” keyword. ;-)

        I haven’t done any GUI programming in Python, but I have no doubt that libraries must use a context manager to handle changing/restoring the cursor, and if not it would be trivial to implement.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: