NOTE: Bear with me, I feel a little "flame grilled" due to some discussions over here and here and some issues I reported here and here.
Some background
Ye olde (pre 10.4) FreeAndNil
looked like this:
FreeAndNil(var SomeObject)
The new and fresh FreeAndNil
looks like this:
FreeAndNil(const [ref] SomeObject: TObject);
IMO both have their downsides:
- The old one doesn't do any type checking, so calling
FreeAndNil
on pointers, records and interfaces compiles just fine, but produces interesting but usually unwanted effects during runtime. (Goes completely berserk or if you are lucky it halts with EAccessViolation, EInvalidOperation etc.) - The new one accepts a const parameter, and therefore any object. But then the provided object pointer is actually changed using some hacky-wacky code.
- You can now call the new
FreeAndNil
like this:FreeAndNil(TObject.Create)
and it will compile and even run just fine. I liked the oldFreeAndNil
that warned me when I went wrong and provided e.g. a property instead of a field. Unsure what happens if you provide a object type property to thisFreeAndNil
implementation. Didn't try.
If we would change the signature into FreeAndNil(var SomeObject:TObject)
then it will not allow us to pass any other variable type then exactly the TObject
type. Which also makes sense, as if it weren't FreeAndNil
, one could easily change a variable provided as type TComponent
in the routine change the var variable into an object of a completely different type, e.g. TCollection
. Of course FreeAndNil
will do no such thing, as it always changes the var parameter to nil.
So this makes FreeAndNil
a special case.
Maybe even special enough to convince delphi to add a compiler magic FreeAndNil
implementation? Votes anyone?
Potential work-around
I came up with the code below as an alternative (here as a helper method, but could as well be part of TObject
implementation) which kind-a combines both worlds. The Assert
will help finding invalid calls during runtime.
procedure TSGObjectHelper.FreeAndNilObj(var aObject);
begin
if Assigned(self) then
begin
Assert(TObject(aObject)=self,ClassName+'.FreeAndNil Wrong parameter provided!');
pointer(aObject):=nil;
Destroy;
end;
end;
Usage would be something like this:
var MyObj:=TSOmeObject.Create;
...
MyObj.FreeAndNilObj(MyObj);
I have actually tested this routine, and it even is slightly faster than the 10.4 FreeAndNil
implementation. I guess because I do the assignment check first and call Destroy
directly.
What I do not like so much is that:
- the type checking takes place during runtime, and then only if Assertions are ON.
- it feels like having to pass the same variable twice. Which isn't necessarily true/required. It has to be the same object, and the parameter has to be a variable.
Another investigation
But wouldn't it be great if one could call without the parameter
var MyObj:=TSomeObject.Create;
...
MyObj.FreeAndNil;
So I messed around with the self
pointer and managed to set it to nil
using the same Hacky-Wacky code that 10.4 utilizes in their FreeAndNil
. Well... that worked inside the method, self
pointed to nil
. But after calling FreeAndNil
like this, the MyObj variable wasn't nil, but a stale pointer. (This was what I expected.) Moreover, MyObj
could be a property or (the result of) a routine, constructor etc.
so nope over here as well...
And finally the question:
Can you think of a cleaner/better solution or trick that would:
- FreeAndNil(var aObject:TObject) with not-so-strict type checking compile time (maybe a Compiler directive?) so it allows compiling and calling for variables of any object type.
- Complains compile time when something is passed that is not a variable/field of some object type
- Help describing what is the best solution/requirement in RSP-29716
FreeAndNil
. – CaroylncarpFreeAndNil
with theclass
constraint, but it is a bit too verbose:TFreer<TFrog>.FreeAndNil(MyFrog);
. – Caroylncarp