Как реализовать 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

Быстрый короткий ответ

Существуют группы интерфейсов TWO​​strong > с одинаковым именем, например: 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 - это он. Мы закончили.