为什么TEnumerable <T>使用传递方法?

Mas*_*ler 12 delphi generics delphi-2009 data-structures

TEnumerable<T>,所有Generics.Collections容器类的基类都有一个非常奇怪的声明.它看起来像这样:

type
  TEnumerable<T> = class abstract
  protected
    function DoGetEnumerator: TEnumerator<T>; virtual; abstract;
  public
    function GetEnumerator: TEnumerator<T>;
  end;

function TEnumerable<T>.GetEnumerator: TEnumerator<T>;
begin
  Result := DoGetEnumerator;
end;
Run Code Online (Sandbox Code Playgroud)

TEnumerator<T> 同样声明一个公共的MoveNext方法和一个私有的DoMoveNext函数,而MoveNext除了调用DoMoveNext之外什么都不做.

除了添加额外的函数调用开销,使调用堆栈更长,并且在尝试从这些基类继承的编码器的头脑中产生混淆之外,任何人都可以向我解释这个服务的目的吗?这种结构化方式是否有任何实际优势,因为如果有,我看不到它......

Bar*_*lly 27

免责声明:我写道TEnumerable<T>.如果我再次这样做的话,我可能会用更少的性能和更简单的方式编写它,因为我已经知道这种优化会让很多人感到困惑.

它旨在避免for-in循环中的虚拟调用,同时保持与多态性的兼容性.这是一般模式:

  • 基类Base定义受保护的虚拟抽象方法V和公共非虚方法M.M调度到V,所以通过Base-typed变量的多态调用将路由到重写的行为V.

  • 后代类如Desc实现静态覆盖M(hidden Base.M)其中包含实现,并实现覆盖V哪些调用Desc.M.无需虚拟调度即可直接调用Mvia Desc-typed变量.

具体示例:当编译器为此序列生成代码时:

var
  someCollection: TSomeCollection<TFoo>;
  x: TFoo;
begin
  // ...
  for x in someCollection do
    // ...
end;
Run Code Online (Sandbox Code Playgroud)

...编译器查找一个GetEnumerator在静态类型上someCollection调用的方法,以及一个MoveNext在它返回的类型上调用的方法(类似于Current属性).如果此方法具有静态分派,则可以消除虚拟呼叫.

这对循环最重要,因此MoveNext/ Current访问者.但是为了使优化起作用,方法的返回类型GetEnumerator必须是协变的,也就是说,它需要静态返回正确的派生枚举器类型.但是在Delphi中,与C++ [1]不同,不可能使用更多派生的返回类型覆盖祖先方法,因此需要应用相同的技巧以获得不同的原因,以更改后代中的返回类型.

优化还可能允许内联MoveNextGetCurrent方法调用,因为静态编译器很难"看穿"虚拟调用并且仍然很快.

[1] C++支持重写方法的返回值协方差.