Как реализовать IEnumerable <T>?
Как реализовать IEnumerable<T>
?
Фон
Допустим, у меня есть класс, который я хочу реализовать IEnumerable<T>
:
TStackoverflow<T> = class(TInterfacedObject, IEnumerable<T>)
public
{ IEnumerable<T> }
function GetEnumerator: IEnumerator<T>;
end;
var
IEnumerable<TMouse> mices = TStackoverflow<TMouse>.Create;
У меня будет реализация:
TStackoverflow<T> = class(TInterfacedObject, IEnumerable<T>)
public
{ IEnumerable<T> }
function GetEnumerator: IEnumerator<T>;
end;
function TStackoverflow<T>.GetEnumerator: IEnumerator<T>;
begin
Result := {snip, not important here};
end;
Теперь, чтобы быть хорошим программистом, я выберу, что мой класс также будет поддерживать интерфейс IEnumerable
:
TStackoverflow<T> = class(TInterfacedObject, IEnumerable<T>, IEnumerable)
public
{ IEnumerable<T> }
function GetEnumerator: IEnumerator<T>;
{ IEnumerable }
function GetEnumerator: IEnumerator;
end;
function TStackoverflow<T>.GetEnumerator: IEnumerator<T>;
begin
Result := {snip, not important here};
end;
function TStackoverflow.GetEnumerator: IEnumerator;
begin
Result := {snip, not important here};
end;
Те из вас, кто знает, где это происходит, будут знать, что реализация IEnumerable
- это красная селедка; и это должно было быть сделано в любом случае.
Теперь возникает проблема
Этот код не компилируется, потому что:
function GetEnumerator: IEnumerator<T>;
function GetEnumerator: IEnumerator;
У меня есть два метода с одной и той же сигнатурой (не совсем одна и та же подпись, но такая же, что Delphi не может различать между ними):
E2254 Перегруженная процедура "GetEnumerator" должна быть отмечена директивой "перегрузка"
Хорошо, отлично, я буду отмечать их как overloads
:
function GetEnumerator: IEnumerator<T>; overload;
function GetEnumerator: IEnumerator; overload;
Но это не работает:
E2252 Метод 'GetEnumerator' с идентичными параметрами уже существует
Разрешение метода интерфейса
Перегрузка была неправильным подходом, мы должны искать предложения о разрешении метода:
type
TStackoverflow<T> = class(TInterfacedObject, IEnumerable<T>, IEnumerable)
protected
function GetEnumeratorTyped: IEnumerator<T>;
function GetEnumeratorGeneric: IEnumerator;
public
function IEnumerable<T>.GetEnumerator = GetEnumeratorTyped;
function IEnumerable.GetEnumerator = GetEnumeratorGeneric;
end;
{ TStackoverflow }
function TStackoverflow<T>.GetEnumeratorGeneric: IEnumerator;
begin
end;
function TStackoverflow<T>.GetEnumeratorTyped: IEnumerator<T>;
begin
end;
Кроме того, это не скомпилируется ни по причинам, которые меня избегают:
E2291 Отсутствует реализация метода интерфейса IEnumerable.GetEnumerator
Итак, давайте забудем IEnumerable
Pretend Мне все равно, не поддерживает ли мой класс IEnumerable
, удаляет его как поддерживаемый интерфейс. Это ничего не меняет, поскольку IEnumerable<T>
происходит от IEnumerble
:
IEnumerable<T> = interface(IEnumerable)
function GetEnumerator: IEnumerator<T>;
end;
поэтому метод должен существовать, а код, который удаляет IEnumerable
:
type
TStackoverflow<T> = class(TInterfacedObject, IEnumerable<T>)
protected
function GetEnumeratorTyped: IEnumerator<T>;
public
function IEnumerable<T>.GetEnumerator = GetEnumeratorTyped;
end;
{ TStackoverflow }
function TStackoverflow<T>.GetEnumeratorTyped: IEnumerator<T>;
begin
end;
не компилируется по той же причине:
E2291 Отсутствует реализация метода интерфейса IEnumerable.GetEnumerator
Хорошо, тогда забыть дженерики
Так что давайте остановимся, будем сотрудничать и слушать. IEnumerable вернулся как новое новое изобретение:
type
TStackoverflow = class(TInterfacedObject, IEnumerable)
public
function GetEnumerator: IEnumerator;
end;
{ TStackoverflow }
function TStackoverflow.GetEnumerator: IEnumerator;
begin
end;
Отлично, это работает. Я могу получить поддержку класса IEnumerable
. Теперь я хочу поддерживать IEnumerable<T>
.
Что приводит меня к моему вопросу:
Как реализовать IEnumerable <T> ?
Обновить: забыл приложить полный нефункциональный код:
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
type
TStackoverflow<T> = class(TInterfacedObject, IEnumerable<T>, IEnumerable)
protected
function GetEnumeratorTyped: IEnumerator<T>;
function GetEnumeratorGeneric: IEnumerator;
public
function IEnumerable<T>.GetEnumerator = GetEnumeratorTyped;
function IEnumerable.GetEnumerator = GetEnumeratorGeneric;
end;
{ TStackoverflow<T> }
function TStackoverflow<T>.GetEnumeratorGeneric: IEnumerator;
begin
end;
function TStackoverflow<T>.GetEnumeratorTyped: IEnumerator<T>;
begin
end;
begin
try
{ TODO -oUser -cConsole Main : Insert code here }
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Ответы
Ответ 1
A предложение разрешения метода может выполнить задание:
type
TStackoverflow<T> = class(TInterfacedObject, IEnumerable<T>, IEnumerable)
public
{ IEnumerable<T> }
function GetEnumeratorGeneric: IEnumerator<T>;
function IEnumerable<T>.GetEnumerator = GetEnumeratorGeneric;
{ IEnumerable }
function GetEnumerator: IEnumerator;
function IEnumerable.GetEnumerator = GetEnumerator;
end;
Ваша попытка не удалась, кажется, потому что ни один из ваших методов не был назван GetEnumerator
. В моем коде выше один из них называется GetEnumerator
, а другому - другое имя. Из моих экспериментов вы должны иметь точно один из методов реализации, имеющих то же имя, что и метод интерфейса.
Что странно. Я бы сказал, что это похоже на ошибку компилятора. Поведение сохраняется даже до XE7.
С другой стороны, по крайней мере, у вас есть способ двигаться вперед.
Обновление
Хорошо, комментарий Штефана ниже приводит меня, с опозданием, понять, что на самом деле происходит. Вам действительно нужно удовлетворить три метода интерфейса здесь. Очевидно, что для выполнения IEnumerable
мы должны обеспечить реализацию для IEnumerable.GetEnumerator
. Но так как IEnumerable<T>
происходит от IEnumerable
, то IEnumerable<T>
содержит два метода: IEnumerable.GetEnumerator
и IEnumerable<T>.GetEnumerator
. Итак, что вы действительно хотите сделать, это примерно так:
Мне немного сложно отслеживать конфликтующие имена и дженерики, поэтому я собираюсь предложить альтернативный пример, который, я думаю, более четко подчеркивает ключевой момент:
type
IBase = interface
procedure Foo;
end;
IDerived = interface(IBase)
procedure Bar;
end;
TImplementingClass = class(TInterfacedObject, IBase, IDerived)
procedure Proc1;
procedure IBase.Foo = Proc1;
procedure Proc2;
procedure IDerived.Bar = Proc2;
end;
Теперь обратите внимание, что использование наследования интерфейса означает, что IDerived
содержит два метода: Foo
и Bar
.
Приведенный выше код не будет компилироваться. Компилятор говорит:
E2291 Отсутствует реализация метода интерфейса IBase.Foo
Что, конечно, кажется странным, потому что, конечно, дело о разрешении метода касалось этого. Ну, нет, решение метода касалось Foo
, объявленного в IBase
, но не того, что было унаследовано в IDerived
.
В приведенном выше примере мы можем легко решить проблему:
TImplementingClass = class(TInterfacedObject, IBase, IDerived)
procedure Proc1;
procedure IBase.Foo = Proc1;
procedure IDerived.Foo = Proc1;
procedure Proc2;
procedure IDerived.Bar = Proc2;
end;
Этого достаточно, чтобы сделать компилятор счастливым. Теперь мы предоставили реализацию для всех трех методов, которые необходимо выполнить.
К сожалению, вы не можете сделать это с помощью IEnumerable<T>
, потому что унаследованный метод имеет то же имя, что и новый метод, введенный IEnumerable<T>
.
Еще раз спасибо Стефану за то, что он привел меня к просветлению.
Ответ 2
Быстрый короткий ответ
Существуют группы интерфейсов TWOstrong > с одинаковым именем, например: non generic IEnumerable
и: IEnumerable<T>
.
Обзор
Иметь ту же проблему.
Проблема заключается не в реализации интерфейса, а в реализации общего интерфейса.
Существует особая ситуация с такими общими интерфейсами, как IEnumerable<T>
, поскольку они также имеют не общий интерфейс IEnumerable
. Эти интерфейсы совместимы с O.S., они не просто еще один программный интерфейс.
И есть также несколько других общих и не общих родительских интерфейсов, таких как IEnumerator
или IComparable
, которые имеют свои собственные определения членов.
Решение
Чтобы решить ту же проблему, я смотрю, какие родительские интерфейсы, IEnumerable<T>
есть, и обнаруживают соответствующие не общие интерфейсы.
И, добавленный и промежуточный не общий класс, который реализовал не общие интерфейсы. Это позволяет мне легко обнаруживать, какие интерфейсы и соответствующие определения членов отсутствуют.
type
// Important: This intermediate class declaration is NOT generic
// Verify that non generic interface members implemented
TStackoverflowBase = class(TInterfacedObject, IEnumerable)
protected
function GetEnumeratorGeneric: IEnumerator;
public
function IEnumerable.GetEnumerator = GetEnumeratorGeneric;
end;
// Important: This proposed class declaration IS generic,
// yet, it descends from a non generic intermediate class.
// After verify that non generic interface members implemented,
// verify that generic interface members implemented,
TStackoverflow<T> = class(TStackoverflowBase, IEnumerable<T>)
protected
function GetEnumeratorTyped: IEnumerator<T>;
public
function IEnumerable<T>.GetEnumerator = GetEnumeratorTyped;
end;
И вещи сложнее, потому что есть несколько членов, перегруженных одним и тем же идентификатором, но разные типы параметров или разные типы возвращаемых данных,
в зависимости от того, являются ли они общим интерфейсом или не общим интерфейсом.
Решение @David Heffernan, где есть только один класс, неплохо, я попробовал такой же подход, но он был более комм.
Поэтому я применил промежуточный класс, потому что это позволило мне узнать, какие интерфейсы и соответствующие члены, где отсутствует.
Приветствия.
Ответ 3
Собственно, это очень просто, хотя и не очень очевидно. Ключ должен знать, что компилятор делает некоторые магии с счетчиками.
Во-первых, нам вообще не нужно реализовывать IEnumerator
или IEnumerator<T>
. Вместо этого мы объявляем или владеем интерфейсом. Ради этого примера мы перечисляем WideStrings, поэтому он может выглядеть так, как показано ниже. Обратите внимание: здесь важны имена и сигнатуры метода/свойств:
IFoobarEnumerator = interface
function GetCurrent: WideString;
function MoveNext: Boolean;
procedure Reset;
property Current: WideString read GetCurrent;
end;
Теперь окружающий класс получает метод под названием GetEnumerator
, который возвращает что-то похожее на перечислитель.
Я намеренно написал "что-то похожее на перечислитель", а не "что-то, что реализует IEnumerator<T>
", потому что все компиляторы должны позволить магии:
function GetEnumerator : IFoobarEnumerator;
Помимо того факта, что нам, конечно, еще нужно реализовать класс перечислителя для IFoobarEnumerator
- это он. Мы закончили.