Disclaimer: I wrote TEnumerable<T>
. If I were to do it again, I'd probably have written it with less performance and more simplicity in mind, as I've learned this optimization confuses a lot of people.
It's designed to avoid a virtual call in for-in
loops while maintaining compatibility with polymorphism. This is the general pattern:
Base class Base
defines protected virtual abstract method V
and public non-virtual method M
. M
dispatches to V
, so polymorphic calls via a Base
-typed variable will route to overridden behaviour of V
.
Descendant classes such as Desc
implement static override of M
(hiding Base.M
) which contains the implementation, and implement an override of V
which calls Desc.M
. Calls of M
via Desc
-typed variables go direct to the implementation without a virtual dispatch.
Concrete example: when the compiler generates code for this sequence:
var
someCollection: TSomeCollection<TFoo>;
x: TFoo;
begin
// ...
for x in someCollection do
// ...
end;
... the compiler looks for a method called GetEnumerator
on the static type of someCollection
, and a method called MoveNext
on the type it returns (and similarly for Current
property). If this method has static dispatch, a virtual call can be eliminated.
This is most important for loops, and thus MoveNext
/ Current
accessor. But in order for the optimization to work, the return type of the GetEnumerator
method must be covariant, that is, it needs to statically return the correct derived enumerator type. But in Delphi, unlike C++ [1], it is not possible to override an ancestor method with a more-derived return-type, so the same trick needs to be applied for a different reason, to change the return type in descendants.
The optimization also potentially permits inlining of the MoveNext
and GetCurrent
method calls, as it is very difficult for a static compiler to "see through" virtual calls and still be fast.
[1] C++ support return-value covariance on overridden methods.