Just like (some) other programmers, I like to abuse the fact that the Delphi compiler only destroys local interfaces when a method ends. If you don’t know what I’m talking about, check out the latest Nick’s post in the Fun Code of the Week series.
As an example, Nick put together a function which changes application cursor and at the end of the method reverts it back to the previous state.
procedure test; begin AutoCursor(crHourGlass); // some long operation // cursor is automatically reverted when ‘test’ exits end;
My approach to this pattern is usually slightly different. I like to mark the scope of the local interface with a with statement.
procedure test; begin with AutoCursor(crHourGlass) do begin // some long operation end; // cursor is automatically reverted when ‘test’ exits end;
It’s just too bad that compiler still destroys the interface on the final end of the method and not when the with statements ends. Still, this approach allows me to implement an interface method that restores the previous state so I can call it explicitly in the code. Let’s say that the interface returned from the AutoCursor call implements the Restore method which restores the original cursor. Then I’d use the AutoCursor in a following manner.
procedure test; begin with AutoCursor(crHourGlass) do begin // some long operation Restore; end; // cursor is automatically reverted when ‘test’ exits end;
Few days ago I was working on a multithreaded lock manager which will shortly appear in the OmniThreadLibrary. Unimportant details aside, this lock manager supports Lock and Unlock functions and a global function Lock. The latter locks the resource and creates an interface which will unlock the resource through the automatically destroyed interface, just like the AutoCursor from the Nick’s example does.
The code would look approximately like this …
function Lock(const lockManager: ISttdbDatabaseLockManager; const key: string; timeout_ms: cardinal): IAutoUnlock; begin if not lockManager.Lock(key, timeout_ms) then Result := nil else Result := CreateAutoUnlock(lockManager, key); end;
… but just before writing CreateAutoUnlock I had an idea. Actually, there’s no need to write a new auto-something interface for every occasion. I can create just one such interface which executes an anonymous method when it is destroyed. A magic of anonymous methods and variable capturing will take care of the rest.
To bring this (already too long) story to the end – the open-sourced GpStuff unit now contains such a multi-purpose run-code-on-destroy interface called IGpAutoExecute. An instance of this interface is created by the AutoExecute function.
IGpAutoExecute = interface end; function AutoExecute(proc: TProc): IGpAutoExecute; TGpAutoExecute = class(TInterfacedObject, IGpAutoExecute) strict private FProc: TProc; public constructor Create(proc: TProc); destructor Destroy; override; procedure Run; end; constructor TGpAutoExecute.Create(proc: TProc); begin inherited Create; FProc := proc; end; destructor TGpAutoExecute.Destroy; begin Run; inherited; end; procedure TGpAutoExecute.Run; begin if assigned(FProc) then FProc; FProc := nil; end; function AutoExecute(proc: TProc): IGpAutoExecute; begin Result := TGpAutoExecute.Create(proc); end;
With this helper, I could rewrite the Lock function.
function Lock(const lockManager: ISttdbDatabaseLockManager; const key: string; timeout_ms: cardinal): IGpAutoExecute; begin if not lockManager.Lock(key, timeout_ms) then Result := nil else Result := AutoExecute( procedure begin lockManager.Unlock(key); end ); end;
Regardless to everything said above, at the end I created a special unlocking interface which will be used in the OmniThreadLibrary, just because I could then implement Unlock function in it. Still, I think that AutoExecute approach will come handy in the future.