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之外什么都不做。
除了添加额外的函数调用开销,使调用堆栈更长,并且在尝试从这些基类继承的编码器的头脑中产生混淆之外,任何人都可以向我解释它的用途吗?这种构造它的方式是否有任何实际优势,因为如果有,我看不到它......
答案 0 :(得分:27)
免责声明:我写了TEnumerable<T>
。如果我再次这样做,我可能会用更少的性能和更简单的方式编写它,因为我已经了解到这种优化会让很多人感到困惑。
它旨在避免for-in
循环中的虚拟调用,同时保持与多态性的兼容性。这是一般模式:
基类Base
定义受保护的虚拟抽象方法V
和公共非虚方法M
。 M
发送给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>
优化还可能允许内联Current
和GetEnumerator
方法调用,因为静态编译器很难“看到”虚拟调用并且仍然很快。
[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()。