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

时间:2009-08-04 21:38:23

标签: delphi generics data-structures delphi-2009

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;

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

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

3 个答案:

答案 0 :(得分:27)

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

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

  • 基类Base定义受保护的虚拟抽象方法V和公共非虚方法MM发送给V,因此通过Base类型变量的多态调用将路由到V的重写行为。

  • Desc等后代类实现包含实现的M(隐藏Base.M)的静态覆盖,并实现V的覆盖,调用{ {1}}。通过Desc.M类型变量调用M直接进入实施,无需虚拟调度。

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

Desc

...编译器在静态类型var someCollection: TSomeCollection<TFoo>; x: TFoo; begin // ... for x in someCollection do // ... end; 上查找名为GetEnumerator的方法,并在其返回的类型上查找名为someCollection的方法(类似于{{1}属性)。如果此方法具有静态分派,则可以消除虚拟呼叫。

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

优化还可能允许内联CurrentGetEnumerator方法调用,因为静态编译器很难“看到”虚拟调用并且仍然很快。

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

答案 1 :(得分:3)

你将无法像重新引入GetEnumerator:

这样的技巧
function TDictionary<TKey, TValue>.TKeyCollection.DoGetEnumerator: TEnumerator<TKey>;
begin
  Result := GetEnumerator;
end;
...
function TDictionary<TKey, TValue>.TKeyCollection.GetEnumerator: TKeyEnumerator;
begin
  Result := TKeyEnumerator.Create(FDictionary);
end;

创建适当的本地/特定TEnumerator

答案 2 :(得分:3)

GetEnumerator()方法不是虚拟的;你无法覆盖它。这是确保GetEnumerator()始终存在的一种方法,总是采用一组固定的参数(在本例中为none),并且某些程序员不会为后代类弄乱它。任何使用TEnumerable或后代的人都可以调用GetEnumerator()。

但是由于会有不同的TEnumerable后代执行不同的操作,因此DoGetEnumerator()允许程序员在结构内部进行更改。 “虚拟”允许覆盖该方法。 “抽象”强制后代类实现该方法 - 编译器不会让你忘记。由于DoGetEnumerator()被声明为受保护(至少在此级别),因此使用TEnumerable后代的程序员无法绕过GetEnumerator()并直接调用DoGetEnumerator()。