一些背景信息
古老的(10.4之前的)
FreeAndNil
看起来像这样:FreeAndNil(var SomeObject)
新的和更新的FreeAndNil
看起来像这样:
FreeAndNil(const [ref] SomeObject: TObject);
我认为两种方法都有其缺点:
- 旧方法没有进行任何类型检查,因此在指针、记录和接口上调用
FreeAndNil
可以编译通过,但是在运行时会产生有趣但通常不想要的效果(完全失控或者幸运的话会停止并显示EAccessViolation、EInvalidOperation等错误信息)。 - 新方法接受const参数,因此可以接受任何对象。但是提供的对象指针实际上是通过某些hacky-wacky代码更改的。
- 现在可以像这样调用新的
FreeAndNil
:FreeAndNil(TObject.Create)
,它可以编译并且运行良好。我喜欢旧的FreeAndNil
,它会在我犯错并提供属性而不是字段时警告我。不确定如果向这个FreeAndNil
实现提供一个对象类型属性会发生什么。我没有尝试过。
FreeAndNil(var SomeObject:TObject)
,那么它将不允许我们传递除TObject
类型之外的任何其他变量类型。这也是有道理的,因为如果不是FreeAndNil
,则可以轻松地将作为类型TComponent
提供的变量更改为完全不同类型的对象,例如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.4的FreeAndNil实现略快。我猜是因为我首先进行赋值检查并直接调用Destroy。
但我不太喜欢的是:
- 类型检查发生在运行时,并且仅在启用断言时才会检查。 - 感觉就像必须两次传递相同的变量。这不一定是真的/必需的。它必须是同一个对象,并且参数必须是一个变量。
另一个调查
但如果可以不带参数调用,那不是很好吗?
var MyObj:=TSomeObject.Create;
...
MyObj.FreeAndNil;
我搞乱了`self`指针,并使用10.4中的`FreeAndNil`所使用的Hacky-Wacky代码将其设置为`nil`。嗯…这在方法内部有效,`self`指向`nil`。但是在像这样调用`FreeAndNil`之后,`MyObj`变量不是`nil`,而是一个过时的指针(这是我预期的)。此外,`MyObj`可以是属性或例程、构造函数等的结果。
所以这里也没有解决办法...
最后的问题是:你能想到更干净/更好的解决办法或技巧吗?
- FreeAndNil(var aObject:TObject) 具有不太严格的类型检查编译时间(可能是编译器指令?),因此允许编译和调用任何对象类型的变量。
- 当传递的内容不是某个对象类型的变量/字段时,会在编译时发出警告
- 帮助描述RSP-29716中最佳解决方案/要求