注意:请耐心等待,我感觉有点"火焰烧烤">由于这里和这里的一些讨论以及我在这里和这里报道的一些问题。
一些背景
Ye olde(10.4之前)FreeAndNil
看起来是这样的:
FreeAndNil(var SomeObject)
全新的FreeAndNil
如下所示:
FreeAndNil(const [ref] SomeObject: TObject);
IMO都有缺点:
- 旧版本不进行任何类型检查,因此在指针、记录和接口上调用
FreeAndNil
编译得很好,但在运行时会产生有趣但通常不需要的效果。(完全失控,或者如果幸运的话,它会因EAccessViolation、EInvalidOperation等而停止。) - 新的一个接受const参数,因此接受任何对象。但随后提供的对象指针实际上使用了一些古怪的代码进行了更改
- 现在可以这样调用新的
FreeAndNil
:FreeAndNil(TObject.Create)
,它将编译甚至运行得很好。我喜欢旧的FreeAndNil
,它在我出错时警告我,并提供了一个属性而不是字段。不确定如果为此FreeAndNil
实现提供对象类型属性会发生什么。没有尝试
如果我们将签名更改为FreeAndNil(var SomeObject:TObject)
,那么它将不允许我们传递任何其他变量类型,而恰恰是TObject
类型。这也是有道理的,就好像它不是FreeAndNil
一样,可以很容易地更改例程中以类型TComponent
提供的变量,将var变量更改为完全不同类型的对象,例如TCollection
。当然,FreeAndNil
不会做这样的事情,因为它总是将var参数更改为nil。
所以这使得FreeAndNil
成为一个特例甚至可能特别到足以说服delphi添加编译器魔术FreeAndNil
实现?有人投票吗?
围绕的潜在工作
我提出了下面的代码作为一个替代方案(这里是一个辅助方法,但也可以是TObject
实现的一部分),它将两个世界结合在一起。Assert
将有助于在运行时查找无效调用。
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;
用法如下:
var MyObj:=TSOmeObject.Create;
...
MyObj.FreeAndNilObj(MyObj);
实际上,我已经测试了这个例程,它甚至比10.4FreeAndNil
的实现稍微快一些。我想是因为我先做分配检查,然后直接调用Destroy
。我所做的不喜欢的是:
- 类型检查在运行时进行,然后仅当断言为ON时进行
- 感觉就像必须两次传递相同的变量。这不一定是真的/必需的。它必须是同一个对象,并且参数必须是一个变量
另一项调查
但是,如果一个人可以在没有参数的情况下调用,那不是很好吗
var MyObj:=TSomeObject.Create;
...
MyObj.FreeAndNil;
因此,我篡改了self
指针,并使用10.4在其FreeAndNil
中使用的相同的Hacky-Wacky代码将其设置为nil
。好在方法内部起作用的self
指向nil
。但是在这样调用FreeAndNil
之后,MyObj变量不是nil,而是一个过时的指针。(这正是我所期望的。)此外,MyObj
可以是一个属性,也可以是例程、构造函数等的结果。
所以nope也在这里。。。
最后一个问题是:
你能想出一个更清洁/更好的解决方案或技巧吗:
- FreeAndNil(var aObject:TObject)具有不那么严格的类型检查编译时(可能是编译器指令?),因此它允许编译和调用任何对象类型的变量
- 当传递而不是某个对象类型的变量/字段时,会抱怨编译时
- 帮助描述RSP-29716中的最佳解决方案/要求
FreeAndNil
的唯一正确解决方案是通用var参数:
procedure FreeAndNil<T: class>(var Obj: T); inline;
但是,目前Delphi编译器不允许在独立的过程和函数上使用泛型https://quality.embarcadero.com/browse/RSP-13724
不过,这并不意味着不能有通用的FreeAndNil
实现,只是它会比必要的更详细一些。
type
TObj = class
public
class procedure FreeAndNil<T: class>(var Obj: T); static; inline;
end;
class procedure TObj.FreeAndNil<T>(var Obj: T);
var
Temp: TObject;
begin
Temp := Obj;
Obj := nil;
Temp.Free;
end;
Rio中引入的类型推断将允许您在不指定通用签名的情况下调用它:
TObj.FreeAndNil(Obj);
在旧的Delphi版本中调用(并使用)通用FreeAndNil
也是可能的,但更详细的
TObj.FreeAndNil<TFoo>(Obj);
因为我们不能创建全局procedure FreeAndNil<T:class>(var aObject:T)
,我建议将下面的代码作为TObject类的方法。(rtl更改将由embacadero进行,但不需要编译器更改)
class procedure TObject.InternalFreeAndNil(var Object:TObject); static; // strict private class method
begin
if Assigned(Object) then
begin
var tmp:=Object;
Object:=nil;
tmp.Destroy;
end;
end;
class procedure TObject.FreeAndNil<T:class>(var Object:T); inline; // public generic class method
begin
InternalFreeAndNil(TObject(Object));
end;
并且从CCD_ 32单元移除当前(10.4及更早)CCD_。
当从任何其他方法中调用新的通用FreeAndNil
方法时,可以简单地调用:
FreeAndNil(SomeObjectVariable)
和10.3+类型推理避免了必须写入:
FreeAndNil<TMyClassSpec>(SomeObjectVariable)
这很好,因为您的大多数代码都会很好地编译,而不会发生任何更改。
在其他一些地方,例如全局例程和initialization / finalization
部分,必须调用:
TObject.FreeAndNil(SomeObjectVariable)
这对我来说是可以接受的,而且比当前和历史上使用FreeAndNil(const [ref] aObject:TObject)
或非类型FreeAndNil(var aObject)
的中途解决方案要好得多
由于例程非常简单,而且性能似乎是一个问题,人们可能会认为它有一个汇编程序实现。尽管我不确定这是否适用于泛型方法(最好是inline)。
FTM:也可以保留FreeAndNil(var aObject:TObject)
,并告诉人们进行如下类型转换,这也避免了编译器抱怨var类型。但在这种情况下,可能需要调整很多源代码。另一方面,它节省了代码膨胀,仍然避免了函数结果、属性或无效类型(如记录和指针)作为FreeAndNil
的参数的无效使用,并且更改/实现非常简单。
...
var Obj:=TSomeObject.Create;
try
DoSOmethingUseFulWithObj(Obj);
finally
FreeAndNil(TObject(Obj)); // typecast avoids compiler complaining. Compiler wont allow invalid typecasts
end;
...