TEnumerable<T>
, the base class for all the Generics.Collections container classes, has a very strange declaration. It looks like this:
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>
likewise declares a public MoveNext method and a private DoMoveNext function, and MoveNext does nothing but call DoMoveNext.
Can anyone explain to me just what purpose this serves, aside from adding extra function call overhead, making call stacks longer, and creating confusion in the minds of coders attempting to inherit from these base classes? Is there any actual advantage to this way of structuring it, because if there is I don't see it...
The AsEnumerable(Of TSource)(IEnumerable(Of TSource)) method has no effect other than to change the compile-time type of source from a type that implements IEnumerable(Of T) to IEnumerable(Of T) itself. Share Follow edited Jan 6 '10 at 15:38
Enumerates a sequence and produces an immutable hash set of its contents. ToImmutableHashSet<TSource>(IEnumerable<TSource>, IEqualityComparer<TSource>) Enumerates a sequence, produces an immutable hash set of its contents, and uses the specified equality comparer for the set type.
The advantage of AsEnumerable is that it doesn’t force immediate query execution, nor does it create any storage structure. Share Follow answered Jan 6 '17 at 9:33 TimelessTimeless 6,77488 gold badges5656 silver badges9292 bronze badges Add a comment | 3 It's just a nicest and shortest way to cast to an IEnumerable.
Without using AsEnumerable (i.e. just casting) it will make the query generator attempt to create an expression tree for remote execution that contains the local method. Putting AsEnumerable into the query will cause the rest of that query to be executed locally on the results of the remote query.
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.
The GetEnumerator() method isn't virtual; you can't override it. It's one way of ensuring that GetEnumerator() will always exist, always take a fixed set of parameters (none in this case) and that some programmer won't mess it up for descendant classes. Anyone that uses TEnumerable - or a descendant - can call GetEnumerator().
But since there will be different TEnumerable descendants that do different things, the DoGetEnumerator() allows a programmer to make changes internal to the structure. The "virtual" allows the method to be overridden. The "abstract" forces descendant classes to implement the method - the compiler won't allow you to forget. And since DoGetEnumerator() is declared as protected (at least at this level), a programmer using a TEnumerable descendant can't bypass GetEnumerator() and call DoGetEnumerator() directly.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With