Версия для печати
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум на Исходниках.RU > Delphi: Общие вопросы > IEnumerator<T>


Автор: Cfon 01.04.19, 18:13
Здрасте! Я все туплю :D
Пробую прикрутить енумератор к классу TPerson, но ошибка:
[dcc32 Error]: E2291 Missing implementation of interface method IEnumerator.GetCurrent
Имеем следующую реализацию, что ту не так?
Вроде метод GetCurrent реализован, но компиль не видит его :wacko:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    program PersonEnumeratorDemo;
     
    {$APPTYPE CONSOLE}
     
    {$R *.res}
     
    uses System.SysUtils;
     
    type
      TPerson = class
        Name: string;
        Age: integer;
        constructor Create(AName: string; AAge: integer);
      end;
     
      TPersonList = class
      public
        FPersons: TArray<TPerson>;
      public
        function GetEnumerator: IEnumerator<TPerson>;
        procedure Add(APerson: TPerson);    
      end;
        
      TPersonEnumerator = class(TInterfacedObject, IEnumerator<TPerson>)
      private
        FPersons: TArray<TPerson>;
        FIndex: integer;
     
        function GetCurrent: TPerson;    
      public
        constructor Create(APersons: TArray<TPerson>);
        function MoveNext: Boolean;
        procedure Reset;
        property Current: TPerson read GetCurrent;
      end;
     
    { TPerson }
     
    constructor TPerson.Create(AName: string; AAge: integer);
    begin
      Name:= AName; Age:= AAge;
    end;
     
    { TPersonList }
     
    procedure TPersonList.Add(APerson: TPerson);
    begin
      FPersons:= FPersons + [APerson];
    end;
     
    function TPersonList.GetEnumerator: IEnumerator<TPerson>;
    begin
      Result:= TPersonEnumerator.Create(FPersons);
    end;
     
    { TPersonEnumerator }
     
    constructor TPersonEnumerator.Create(APersons: TArray<TPerson>);
    begin
      FPersons:= APersons;
      FIndex:= -1;
    end;
     
    function TPersonEnumerator.GetCurrent: TPerson;
    begin
      Result:= FPersons[FIndex];
    end;
     
    function TPersonEnumerator.MoveNext: Boolean;
    begin
      Result:= FIndex < High(FPersons);
      if Result then Inc(FIndex);
    end;
     
    procedure TPersonEnumerator.Reset;
    begin
      FIndex:= -1;
    end;
     
    begin
      
    end.

Автор: Shaggy 01.04.19, 22:40
требуется реализация
function GetCurrent: TObject;
из IEnumerator, а не IEnumerator<TPerson>
что, очевидно, в одном классе сделать нельзя.
требуется промежуточный (TBasePersonEnumerator - TInterfacedObject и IEnumerator)
и конечный (TPersonEnumerator - TBasePersonEnumerator и IEnumerator<TPerson>)

а почему именно интерфейс?
uses generics.collections
и унаследуйся от TEnumerator<TPerson>

Автор: Fr0sT 02.04.19, 07:19
Энумератор достаточно сделать записью.

Автор: Cfon 02.04.19, 10:41
Цитата Shaggy @
требуется реализация
function GetCurrent: TObject;
из IEnumerator, а не IEnumerator<TPerson>
что, очевидно, в одном классе сделать нельзя.
требуется промежуточный (TBasePersonEnumerator - TInterfacedObject и IEnumerator)
и конечный (TPersonEnumerator - TBasePersonEnumerator и IEnumerator<TPerson>)

а почему именно интерфейс?
uses generics.collections
и унаследуйся от TEnumerator<TPerson>

Вау! Пашет! Спасибо! :thanks:
Протестил и так и так :D
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    uses System.SysUtils;
     
    type
      ....
     
      TBasePersonEnumerator = class (TInterfacedObject, IEnumerator)
      private
        FPersons: TArray<TPerson>;
        FIndex: integer;
        function GetCurrent: TObject;
      public
        constructor Create(APersons: TArray<TPerson>);
        function MoveNext: Boolean;
        procedure Reset;
      end;
     
      TPersonEnumerator = class(TBasePersonEnumerator, IEnumerator<TPerson>)
      private
         function GetCurrent: TPerson;
      public
        constructor Create(APersons: TArray<TPerson>);
        property Current: TPerson read GetCurrent;
      end;
     
    ....
     
    { TBasePersonEnumerator }
     
    constructor TBasePersonEnumerator.Create(APersons: TArray<TPerson>);
    begin
      FPersons:= APersons;
      FIndex:= -1;
    end;
     
    function TBasePersonEnumerator.GetCurrent: TObject;
    begin
      Result:= FPersons[FIndex];
    end;
     
    function TBasePersonEnumerator.MoveNext: Boolean;
    begin
      Result:= FIndex < High(FPersons);
      if Result then Inc(FIndex);
    end;
     
    procedure TBasePersonEnumerator.Reset;
    begin
      FIndex:= -1;
    end;
     
    { TPersonEnumerator }
     
    constructor TPersonEnumerator.Create(APersons: TArray<TPerson>);
    begin
      inherited Create(APersons);
    end;
     
    function TPersonEnumerator.GetCurrent: TPerson;
    begin
      Result:= TPerson(inherited GetCurrent);
    end;
     
    //  test
    begin
      var persons:= TPersonList.Create;
      try
        persons.Add(TPerson.Create('Gregory', 42));
        persons.Add(TPerson.Create('Mark', 27));
        persons.Add(TPerson.Create('Julia', 32));
     
        for var p in persons do
        begin
          Writeln(Format('%s is %d years old.', [p.Name, p.Age]));
        end;
      finally
        persons.Free;
      end;
    end.


Добавлено
Цитата Fr0sT @
Энумератор достаточно сделать записью.

Это как? :D
Или имеется ввиду просто без реализации IEnumerator? Так это я знаю, мене интересовала реализаци IEnumerator<T> :D

Автор: Fr0sT 02.04.19, 12:50
А зачем IEnumerator вообще нужен?

Автор: Cfon 02.04.19, 19:38
Цитата Fr0sT @
А зачем IEnumerator вообще нужен?

Я без понятия :D
почитайте книжку дяди Коли 'Фигачим на Делфине ' :lool:

Автор: Cfon 03.04.19, 04:25
Цитата Shaggy @
требуется реализация
function GetCurrent: TObject;
из IEnumerator, а не IEnumerator<TPerson>
что, очевидно, в одном классе сделать нельзя.

Можно! :D
http://docwiki.embarcadero.com/RADStudio/Rio/en/Implementing_Interfaces
см. раздел Method Resolution Clause.
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    TPersonEnumerator = class(TInterfacedObject, IEnumerator<TPerson>)
      private
        FPersons: TArray<TPerson>;
        FIndex: integer;
     
        function GetCurrent: TObject; //<-- 1
        function GetCurrentPerson: TPerson; //<-- 2
        function IEnumerator<TPerson>.GetCurrent = GetCurrentPerson; //<-- 3
      public
        constructor Create(APersons: TArray<TPerson>);
        function MoveNext: Boolean;
        procedure Reset;
        property Current: TPerson read GetCurrentPerson;
      end;
     
    { TPersonEnumerator }
     
    constructor TPersonEnumerator.Create(APersons: TArray<TPerson>);
    begin
      FPersons:= APersons;
      FIndex:= -1;
    end;
     
    function TPersonEnumerator.GetCurrent: TObject;
    begin
      Result:= FPersons[FIndex];
    end;
     
    function TPersonEnumerator.GetCurrentPerson: TPerson;
    begin
      Result:= TPerson(GetCurrent);
    end;
     
    function TPersonEnumerator.MoveNext: Boolean;
    begin
      Result:= FIndex < High(FPersons);
      if Result then Inc(FIndex);
    end;
     
    procedure TPersonEnumerator.Reset;
    begin
      FIndex := -1;
    end;

Автор: jack128 03.04.19, 08:13
Цитата Fr0sT @
А зачем IEnumerator вообще нужен?

Потому что эмбаркадеро бездумно скопировало эту иерархию с .NET без оглядки на отличия дотнетовской и дельфийской системы типов.
В .NET любой тип (в том числе числа, структуры (аналог дельфийского record) - является наследником object. А значит перечисление(Enumerable) любого типа можно представить как перечисление объектов. Это может быть полезно для неких обобщенных алгоритмов, ну например:

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    void WriteItemsToConsole(IEnumerable items) {
        foreach(var item in items) Console.WriteLine(item.ToString());
    }
    WriteItemsToConsole(new double[] {1.1,2.1,3.14});


Но в дельфе список тех же Double не возможно представить в виде объектов. Так что иерархия System.IEnumerable - сломана.
В том же spring4d своя иерархия IEnumerable и НЕдженерик IEnumerable - это список Rtti.TValue, там Стефан нормально сделал, любой тип может быть представлен как TValue

Автор: Cfon 04.04.19, 16:36
Пробую заюзать Spring4D в моем примере, и вроде бы все верно но ошибка доступа по нолевому адресу :huh:
Немного дебагнул выяснил что итерация идет даже когда список исчерпан :wacko:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    uses
      System.SysUtils
    , System.RTTI
    , Spring.Collections
    , Spring.Collections.Lists
    ;
     
    type
      TPerson = class
        Name: string;
        Age: integer;
        constructor Create(AName: string; AAge: integer);
      end;
     
      TPersonList = class(TObjectList<TPerson>)
      public    
        function GetEnumerator: IEnumerator<TPerson>; override;
      end;
     
      TPersonEnumerator = class(TInterfacedObject, IEnumerator<TPerson>)
      private
        FPersons: TArray<TPerson>;
        FIndex: integer;
     
        function GetCurrent: TValue;
        function GetCurrentPerson: TPerson;
        function IEnumerator<TPerson>.GetCurrent = GetCurrentPerson;
      public
        constructor Create(APersons: TArray<TPerson>);
        function MoveNext: Boolean;
        procedure Reset;
        property Current: TPerson read GetCurrentPerson;
      end;
     
    { TPerson }
     
    constructor TPerson.Create(AName: string; AAge: integer);
    begin
      Name:= AName; Age:= AAge;
    end;
     
    { TPersonList }
     
    function TPersonList.GetEnumerator: IEnumerator<TPerson>;
    begin
      Result:= TPersonEnumerator.Create(GetItems);
    end;
     
    { TPersonEnumerator }
     
    constructor TPersonEnumerator.Create(APersons: TArray<TPerson>);
    begin
      FPersons:= APersons;
      FIndex:= -1;
    end;
     
    function TPersonEnumerator.GetCurrent: TValue;
    begin
      Result:= FPersons[FIndex];
    end;
     
    function TPersonEnumerator.GetCurrentPerson: TPerson;
    begin
      Result:= GetCurrent.AsType<TPerson>;
    end;
     
    function TPersonEnumerator.MoveNext: Boolean;
    begin
      Result:= FIndex < High(FPersons);
      if Result then Inc(FIndex);
    end;
     
    procedure TPersonEnumerator.Reset;
    begin
      FIndex := -1;
    end;
     
    // test
    begin
      var persons:= TPersonList.Create([
        TPerson.Create('Gregory', 42),
        TPerson.Create('Mark', 27)
      ]);
      persons.Add(TPerson.Create('Julia', 32));
     
      var names := TEnumerable.Select<TPerson, string>(persons,
        function (p: TPerson): string
        begin
          Result:= p.Name; //<-- тут AV доступ по нолевому p
        end);
     
      names.ForEach(procedure (const name: string)
        begin
          Writeln(Format('%s', [name]));
        end);
    end;
     
    end.


Проверил число персон в массиве оказалось 4! Откуда хз :D
Применил хак :D в MoveNext енумератора минусовал 1 от High(FPersons) и заработалло, но вопрос остается откуда там 4 морды когда я добавил тока 3 :-?

Проверил на дефолтном енумераторе TObjectList, закомметив свой енум пашет без AV :scratch:
Кажестся понял в TObjectList юзается какой то TArrayManager и вроде бы он добавил еще один итем, ну типо прозапас для эфективности вооот... я ж получаю получается этот сырой массив! :D
Вопрос может я ваще не должен был оверайдить GetEnumerator?

Решил т.о. передал в конструктор енумератора еще и Count и в MoveNext чекал FIndex < FCount - 1 :D

Автор: Fr0sT 05.04.19, 13:08
Да, в List массив создается с запасом

Powered by Invision Power Board (https://www.invisionboard.com)
© Invision Power Services (https://www.invisionpower.com)