代码之家  ›  专栏  ›  技术社区  ›  Mason Wheeler

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

  •  12
  • Mason Wheeler  · 技术社区  · 16 年前

    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 回复  |  直到 16 年前
        1
  •  27
  •   Barry Kelly    16 年前

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

    它的设计是为了避免虚拟呼叫 for-in 循环,同时保持与多态性的兼容性。这是一般模式:

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

    • 后代类,如 Desc 实现的静态重写 (隐藏) Base.M )其中包含实现,并实现对 V 哪些调用 Desc.M . 呼唤 通过 DESC -类型化变量直接指向实现,而不使用虚拟调度。

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

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

    …编译器查找一个名为 GetEnumerator 关于静态类型 someCollection 和一个方法 MoveNext 在它返回的类型上(和类似的 Current 财产)。如果此方法具有静态调度,则可以消除虚拟调用。

    这对循环最重要,因此 移到下一行 / 电流 访问器。但为了优化工作,返回类型为 方法 方法必须是协变的,也就是说,它需要静态地返回正确的派生枚举器类型。但是在Delphi中,与C++(1)不同的是,不可能重写具有更派生的返回类型的祖先方法,因此需要用不同的原因来应用相同的技巧,以改变后代中的返回类型。

    优化还可能允许 移到下一行 GetCurrent 方法调用,因为静态编译器很难“看穿”虚拟调用,而且仍然很快。

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

        2
  •  3
  •   Francesca    16 年前

    您将无法执行诸如重新引入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

        3
  •  3
  •   Jason Swager    16 年前

    getEnumerator()方法不是虚拟的;您不能重写它。这是确保getEnumerator()始终存在的一种方法,总是采用一组固定的参数(在本例中为零),并且某些程序员不会将其用于后代类。任何使用Tenumerable或其后代的人都可以调用GetEnumerator()。

    但是,由于将有不同的可数子代执行不同的操作,dogetEnumerator()允许程序员在结构内部进行更改。“virtual”允许重写该方法。“abstract”强制后代类实现该方法——编译器不会让您忘记。而且由于dogetEnumerator()被声明为受保护(至少在这个级别上),使用可数子代的程序员不能绕过getEnumerator()直接调用dogetEnumerator()。