Delphi FreeAndNil:寻找替代实施

Delphi FreeAndNil: Looking for an alternate implementation

注意:请耐心等待,由于对 here and here and some issues I reported here and here.

的一些讨论,我觉得有点 “火焰烤”

一些背景

Ye olde(10.4 之前)FreeAndNil 看起来像这样:

FreeAndNil(var SomeObject)

新鲜的FreeAndNil长这样:

FreeAndNil(const [ref] SomeObject: TObject);

IMO 都有其缺点:

如果我们将签名更改为 FreeAndNil(var SomeObject:TObject) 那么它将不允许我们传递任何其他变量类型,而恰恰是 TObject 类型。这也是有道理的,就好像它不是 FreeAndNil 一样,可以很容易地更改例程中作为类型 TComponent 提供的变量,将 var 变量更改为完全不同类型的对象,例如TCollection。当然 FreeAndNil 不会做这样的事情,因为它总是将 var 参数更改为 nil。

所以这使得 FreeAndNil 成为一个特例。 甚至可能特别到足以说服 delphi 添加 编译器魔术 FreeAndNil 实现?有人投票吗?

可能的解决方法

我想出了下面的代码作为替代方法(这里作为辅助方法,但也可以是 TObject 实现的一部分),kind-a 结合了两个世界。 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 可以是 属性 或例程、构造函数等的(结果)

所以这里也...

最后是问题:

你能想到一个 cleaner/better 解决方案或技巧吗:

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 class 的方法。 (由 embarcadero 进行 rtl 更改,但不需要编译器更改)

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;

并将当前(10.4 及更早版本)FreeAndNilsysutils 单元中删除以避免歧义。

当从 任何 其他方法中调用新的通用 FreeAndNil 方法时,只需调用:

FreeAndNil(SomeObjectVariable)

和 10.3+ 类型推断避免了必须写:

FreeAndNil<TMyClassSpec>(SomeObjectVariable)

这很好,因为 您的大部分代码无需更改即可编译

在其他一些地方,例如全局例程和 initialization / finalization 部分必须调用:

TObject.FreeAndNil(SomeObjectVariable)

这对我来说是可以接受的,并且比使用 FreeAndNil(const [ref] aObject:TObject) 或无类型 FreeAndNil(var aObject)

的当前和历史中途解决方案要好得多

并且由于该例程非常简单并且性能似乎是一个问题,因此可以争辩为其使用汇编程序实现。虽然我不确定这是否是通用的 allowed/possible,(最好是 inline)方法。

FTM:也可以只保留 FreeAndNil(var aObject:TObject) 并告诉人们进行如下所示的类型转换,这也避免了编译器抱怨 var 类型。但是在这种情况下,可能要调整很多源代码。另一方面,它节省了代码膨胀,仍然避免了无效使用函数结果、属性或无效类型(如记录和指针)作为 FreeAndNil 的参数,并且对 change/implement.[=24= 非常简单]

...
var Obj:=TSomeObject.Create;
try
   DoSOmethingUseFulWithObj(Obj);
finally
  FreeAndNil(TObject(Obj)); // typecast avoids compiler complaining. Compiler wont allow invalid typecasts
end;
...