Отказ от ответственности: Я написал TEnumerable
. Если бы я сделал это снова, я бы, вероятно, написал его с меньшей производительностью и большей простотой, поскольку я узнал, что эта оптимизация сбивает с толку многих людей.
Она разработана, чтобы избежать виртуального вызова в ] for-in
при сохранении совместимости с полиморфизмом. Это общий шаблон:
Базовый класс Базовый
определяет защищенный виртуальный абстрактный метод V
и общедоступный невиртуальный метод M
. M
отправляется на V
, поэтому полиморфные вызовы через переменную типа Base
направляются в переопределенное поведение V
.
Классы-потомки, такие как Desc
, реализуют статическое переопределение M
(скрывая Base.M
), который содержит реализацию, и реализуют переопределение V
, который вызывает Desc.M
. Вызовы M
через переменные типа Desc
идут прямо в реализацию без виртуальной диспетчеризации.
Конкретный пример: когда компилятор генерирует код для этой последовательности:
var
someCollection: TSomeCollection<TFoo>;
x: TFoo;
begin
// ...
for x in someCollection do
// ...
end;
.. .компилятор ищет метод под названием GetEnumerator
для статического типа someCollection
и метод под названием MoveNext
для возвращаемого типа (и аналогично для ] Текущее свойство
). Если этот метод имеет статическую отправку, виртуальный вызов может быть исключен.
Это наиболее важно для петель, и таким образом MoveNext
/ Current
аксессор. Но для того, чтобы оптимизация работала, возвращаемый тип метода GetEnumerator
должен быть ковариантным, то есть он должен статически возвращать правильный производный тип перечислителя. Но в Delphi, в отличие от C ++ [1], невозможно переопределить метод-предок с помощью более производного типа возврата, поэтому тот же трюк необходимо применить по другой причине, чтобы изменить тип возвращаемого значения в потомках.
Оптимизация также потенциально позволяет встраивать вызовы методов MoveNext
и GetCurrent
, поскольку статическому компилятору очень трудно «видеть сквозь» виртуальные вызовы и при этом оставаться быстрым.
[1] C ++ поддерживает ковариацию возвращаемого значения для переопределенных методов.
тип возвращаемого значения метода GetEnumerator
должен быть ковариантным, то есть он должен статически возвращать правильный производный тип перечислителя. Но в Delphi, в отличие от C ++ [1], невозможно переопределить метод-предок более производным типом возврата, поэтому тот же трюк необходимо применить по другой причине, чтобы изменить тип возвращаемого значения в потомках.
Оптимизация также потенциально позволяет встраивать вызовы методов MoveNext
и GetCurrent
, поскольку статическому компилятору очень трудно «видеть сквозь» виртуальные вызовы и при этом оставаться быстрым.
[1] C ++ поддерживает ковариацию возвращаемого значения для переопределенных методов.
тип возвращаемого значения метода GetEnumerator
должен быть ковариантным, то есть он должен статически возвращать правильный производный тип перечислителя. Но в Delphi, в отличие от C ++ [1], невозможно переопределить метод-предок более производным типом возврата, поэтому тот же трюк необходимо применить по другой причине, чтобы изменить тип возвращаемого значения в потомках.
Оптимизация также потенциально позволяет встраивать вызовы методов MoveNext
и GetCurrent
, поскольку статическому компилятору очень трудно «видеть сквозь» виртуальные вызовы и при этом оставаться быстрым.
[1] C ++ поддерживает ковариацию возвращаемого значения для переопределенных методов.
Метод GetEnumerator () не виртуальный; вы не можете отменить это. Это один из способов гарантировать, что GetEnumerator () всегда будет существовать, всегда принимать фиксированный набор параметров (в данном случае их нет) и что какой-то программист не испортит его для классов-потомков. Любой, кто использует TEnumerable - или потомок - может вызвать GetEnumerator ().
Но поскольку будут разные потомки TEnumerable, которые делают разные вещи, DoGetEnumerator () позволяет программисту вносить изменения внутри структуры. «Виртуальный» позволяет переопределить метод. «Абстрактное» заставляет классы-потомки реализовать метод - компилятор не позволит вам забыть. А поскольку DoGetEnumerator () объявлен как защищенный (по крайней мере, на этом уровне), программист, использующий потомка TEnumerable, может '
Вы не сможете выполнять такие трюки, как повторное введение 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