I had this idea this morning on avoiding nested try finally blocks like the following
procedure DoSomething;
var
T1, T2, T3 : TTestObject;
begin
T1 := TTestObject.Create('One');
try
T2 := TTestObject.Create('Two');
try
T3 := TTestObject.Create('Three');
try
//A bunch of code;
finally
T3.Free;
end;
finally
T2.Free;
end;
finally
T1.Free;
end;
end;
By taking advantage of the automated reference counting of interfaces, I have come up with
Type
IDoFinally = interface
procedure DoFree(O : TObject);
end;
TDoFinally = class(TInterfacedObject, IDoFinally)
private
FreeObjectList : TObjectList;
public
procedure DoFree(O : TObject);
constructor Create;
destructor Destroy; override;
end;
//...
procedure TDoFinally.DoFree(O : TObject);
begin
FreeObjectList.Add(O);
end;
constructor TDoFinally.Create;
begin
FreeObjectList := TObjectList.Create(True);
end;
destructor TDoFinally.Destroy;
begin
FreeObjectList.Free;
inherited;
end;
So that the previous block of code becomes
procedure DoSomething;
var
T1, T2, T3 : TTestObject;
DoFinally : IDoFinally;
begin
DoFinally := TDoFinally.Create;
T1 := TTestObject.Create('One');
DoFinally.DoFree(T1);
T2 := TTestObject.Create('Two');
DoFinally.DoFree(T2);
T3 := TTestObject.Create('Three');
DoFinally.DoFree(T3);
// A Bunch of code;
end;
My question is: does this work or have I overlooked something?
To me this looks pretty cool, and makes the code a bit easier to read with the reduced amount of nesting. It could also be extended to store a list of anonymous methods to run to do things such as close files, queries, etc...
Yes, it works.
Perhaps the only thing different between the nested try-finally blocks of the original code and the technique of using a reference-counted object to manage the lifetimes of other objects is what happens if there's a problem destroying any of the objects. If there's an exception while any object is being destroyed, the nested try-finally blocks will ensure that any remaining objects will still get freed. The TObjectList
in your TDoFinally
doesn't do that; if any item in the list can't be destroyed, any subsequent items in the list will be leaked.
In practice, that's not really a problem, though. No destructor should ever throw an exception. If it does, there's not really any way to recover from it anyway, so it doesn't matter if anything leaks because of it. Your program should terminate momentarily anyway, so having a tidy cleanup routine is of little importance.
Incidentally, the JCL already offers the ISafeGuard
and IMultiSafeGuard
interfaces for managing local objects' lifetimes. For example, you could rewrite your code like this:
uses JclSysUtils;
procedure DoSomething;
var
T1, T2, T3: TTestObject;
G: IMultiSafeGuard;
begin
T1 := TTestObject(Guard(TTestObject.Create('One'), G));
T2 := TTestObject(Guard(TTestObject.Create('Two'), G));
T3 := TTestObject(Guard(TTestObject.Create('Three'), G));
// A Bunch of code;
end;
That library doesn't address exceptions in destructors, either.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With