【问题标题】:Why does TEnumerable<T> use pass-through methods?为什么 TEnumerable<T> 使用传递方法?
【发布时间】:2009-08-04 21:38:22
【问题描述】:

TEnumerable&lt;T&gt;,所有 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&lt;T&gt; 同样声明了一个公共的 MoveNext 方法和一个私有的 DoMoveNext 函数,而 MoveNext 除了调用 DoMoveNext 什么都不做。

除了增加额外的函数调用开销、使调用堆栈更长以及在尝试从这些基类继承的编码人员的头脑中造成混乱之外,谁能向我解释它的用途是什么?这种构建它的方式有什么实际优势吗,因为如果有的话我看不到它......

【问题讨论】:

  • 它实际上减少了函数调用开销并使调用堆栈更短...... :)

标签: delphi generics data-structures delphi-2009


【解决方案1】:

免责声明:我写了TEnumerable&lt;T&gt;。如果我再做一次,我可能会在编写它时考虑到更少的性能和更简单的想法,因为我了解到这种优化会让很多人感到困惑。

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

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

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

具体例子:当编译器为这个序列生成代码时:

var
  someCollection: TSomeCollection<TFoo>;
  x: TFoo;
begin
  // ...
  for x in someCollection do
    // ...
end;

...编译器在 someCollection 的静态类型上查找名为 GetEnumerator 的方法,并在其返回的类型上查找名为 MoveNext 的方法(对于 Current 属性也是如此)。如果此方法具有静态调度,则可以消除虚拟调用。

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

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

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

【讨论】:

    【解决方案2】:

    GetEnumerator() 方法不是虚拟的;你不能覆盖它。这是确保 GetEnumerator() 始终存在的一种方法,始终采用一组固定的参数(在这种情况下没有参数),并且某些程序员不会为后代类搞砸它。任何使用 TEnumerable(或后代)的人都可以调用 GetEnumerator()。

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

    【讨论】:

      【解决方案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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-10-18
        • 2020-05-25
        • 1970-01-01
        • 2020-04-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多