Самый простой способ сделать Enumerable коллекцию

Вопросы программирования на Free Pascal, использования компилятора и утилит.

Модератор: Модераторы

Самый простой способ сделать Enumerable коллекцию

Сообщение Climber » 12.10.2021 00:47:12

Вообще не могу нагуглить примеры, приходится идти на ощупь...
Прочитал статью тут: https://wiki.freepascal.org/for-in_loop
Хочу сделать так же, чтобы можно было написать "for MyObj in MyCollection loop"

У меня есть объект-коллекция, что-то вроде такого:
Код: Выделить всё
TMyObject = class
   ...  // тут что-то есть
end;

TMyCollection = class
private
  FItems: array of TMyObject;  // ну или не массив, не важно
public
  ...
end;

Прочитал статью выше, самым простым способом показалось реализовать интерфейс IEnumerator. ОК, пробуем:
Код: Выделить всё
TMyCollection = class(IEnumerator)
  FItems: array of TMyObject;  // ну или не массив, не важно
  function GetCurrent: TObject;
  function MoveNext: Boolean;
  procedure Reset;
  property Current: TObject read GetCurrent;
end;
Написал кое-как имплементации этих методов (кое-как - потому что никаких примеров реализации нагуглить не удалось, писал по наитию).
Компилирую - получаю ошибку: No matching implementation for interface method QueryInterface <что-то там> found.
Нашел совет - добавить директиву "{$interfaces corba}". Добавил - не помогло.
Пока не сдаюсь и продолжаю искать, но советы приветствуются...

P. S. Только дописал пост, как обратил внимание, что в статье выше написано, что "Where IEnumerator is declared as: "
Код: Выделить всё
IEnumerator = interface(IInterface)

А у меня, когда делаю ctrl+click на интерфейсе, написано
Код: Выделить всё
generic IEnumerator<T> = interface

Вот теперь я окончательно запутался. 8) Спасите...
Climber
постоялец
 
Сообщения: 415
Зарегистрирован: 03.06.2007 20:09:57
Откуда: Москва

Re: Самый простой способ сделать Enumerable коллекцию

Сообщение iskander » 12.10.2021 09:26:13

Емнип, всё гораздо проще, но густо замешано на магии компилятора:

  • для контейнера должен быть определён тип енумератора(класс или рекорд);
  • енумератор ДОЛЖЕН содержать метод MoveNext: Boolean и свойство Current, совпадающее с типом элементов контейнера;
  • у контейнера ДОЛЖЕН быть метод GetEnumerator, возвращающий сущность этого типа;
  • если енумератор является классом, компилятор самостоятельно под капотом делает ему Free() по завершении цикла;
Кажется и всё.
iskander
энтузиаст
 
Сообщения: 590
Зарегистрирован: 08.01.2012 18:43:34

Re: Самый простой способ сделать Enumerable коллекцию

Сообщение Climber » 12.10.2021 11:35:24

Да, такой способ в той статье (самая первая ссылка в стартовом посте) тоже есть, и даже с примером. Но для этого способа нужно создать дополнительный класс и реализовать на один метод больше, а для способа с интерфейсом - только добавить интерфейс к классу-контейнеру. Ну я и подумал - способ с интерфейсом короче, попробую сначала его. Ну вот, попробовал :roll: Теперь буду пробовать второй. Уже после того, как пост написал, нашел еще один пример (ссылка осталась дома на домашнем компе, сейчас с работы пишу). Он и выглядит еще короче и проще, и там полный пример - полностью написанная программа, которую запустить можно. Попробую, напишу потом, что получилось.
Climber
постоялец
 
Сообщения: 415
Зарегистрирован: 03.06.2007 20:09:57
Откуда: Москва

Re: Самый простой способ сделать Enumerable коллекцию

Сообщение iskander » 12.10.2021 11:37:26

Ну вот например:
Код: Выделить всё
program test;
{$mode objfpc}{$H+}
{$modeswitch advancedrecords}
uses
  SysUtils;

type
  TDumbEnumerator = class
  private
    FIndex: Integer;
    function Get: string;
  public
    constructor Create;
    destructor Destroy; override;
    function MoveNext: Boolean;
    property Current: string read Get;
  end;

  TDumbList = record
  private
  const
    List: TStringArray = ('one', 'two', 'three', 'four', 'five');
  private
    function GetCount: Integer;
    function GetItem(Index: Integer): string;
  public
    function GetEnumerator: TDumbEnumerator;
    property Count: Integer read GetCount;
    property Items[Index: Integer]: string read GetItem; default;
  end;


  TMyCollection = class
    FItems: array of string;
    FIndex: Integer;
    constructor Create;
    destructor Destroy; override;
    function GetEnumerator: TMyCollection;
    function GetCurrent: string;
    function MoveNext: Boolean;
    procedure Reset;
    property Current: string read GetCurrent;
  end;

function TDumbEnumerator.Get: string;
begin
  Result := TDumbList.List[FIndex];
end;

constructor TDumbEnumerator.Create;
begin
  FIndex := -1;
end;

destructor TDumbEnumerator.Destroy;
begin
  WriteLn('TDumbEnumerator destroyed');
  inherited;
end;

function TDumbEnumerator.MoveNext: Boolean;
begin
  Inc(FIndex);
  Result := FIndex < Length(TDumbList.List);
end;

function TDumbList.GetCount: Integer;
begin
  Result := Length(List);
end;

function TDumbList.GetItem(Index: Integer): string;
begin
  if (Index < 0) or (Index > Count - 1) then
    raise Exception.Create('List index out of bounds');
  Result := List[Index];
end;

function TDumbList.GetEnumerator: TDumbEnumerator;
begin
  Result := TDumbEnumerator.Create;
end;

constructor TMyCollection.Create;
begin
  FItems := ['one', 'two', 'three', 'four', 'five'];
end;

destructor TMyCollection.Destroy;
begin
  WriteLn('Oops! I''m destroyed!');
  inherited;
end;

function TMyCollection.GetEnumerator: TMyCollection;
begin
  Reset;
  Result := Self;
end;

function TMyCollection.GetCurrent: string;
begin
  Result := FItems[FIndex];
end;

function TMyCollection.MoveNext: Boolean;
begin
  Inc(FIndex);
  Result := FIndex < Length(FItems);
end;

procedure TMyCollection.Reset;
begin
  FIndex := -1;
end;

var
  s: string;
  List: TDumbList;
  Coll: TMyCollection;

begin
  for s in List do
    WriteLn(s);
  Coll := TMyCollection.Create;
  for s in Coll do
    WriteLn(s);
  readln;
end. 


EDIT:
Если нужно непременно с интерфейсом:
Код: Выделить всё
program test2;
{$mode objfpc}{$H+}
uses
  SysUtils;

type
{$interfaces corba}
  IEnumerator = interface
    function GetCurrent: string;
    function MoveNext: Boolean;
    procedure Reset;
    property Current: string read GetCurrent;
  end;

  TMyCollection = class(TObject, IEnumerator)
    FItems: array of string;
    FIndex: Integer;
    constructor Create;
    destructor Destroy; override;
    function GetEnumerator: IEnumerator;
    function GetCurrent: string;
    function MoveNext: Boolean;
    procedure Reset;
    property Current: string read GetCurrent;
  end;

constructor TMyCollection.Create;
begin
  FItems := ['one', 'two', 'three', 'four', 'five'];
end;

destructor TMyCollection.Destroy;
begin
  WriteLn('Oops! I''m destroyed!');
  inherited;
end;

function TMyCollection.GetEnumerator: IEnumerator;
begin
  Reset;
  Result := Self;
end;

function TMyCollection.GetCurrent: string;
begin
  Result := FItems[FIndex];
end;

function TMyCollection.MoveNext: Boolean;
begin
  Inc(FIndex);
  Result := FIndex < Length(FItems);
end;

procedure TMyCollection.Reset;
begin
  FIndex := -1;
end;

var
  s: string;
  Coll: TMyCollection;
begin
  Coll := TMyCollection.Create;
  for s in Coll do
    WriteLn(s);
  WriteLn('Loop done');
  Coll.Free; 
end.
iskander
энтузиаст
 
Сообщения: 590
Зарегистрирован: 08.01.2012 18:43:34

Re: Самый простой способ сделать Enumerable коллекцию

Сообщение Climber » 12.10.2021 12:25:12

Спасибо за пример.
Там в той статье было нечто похожее, но без примеров методов MoveNext и Reset, и без какого-либо объяснения, как компилятор их вызывает.
Climber
постоялец
 
Сообщения: 415
Зарегистрирован: 03.06.2007 20:09:57
Откуда: Москва

Re: Самый простой способ сделать Enumerable коллекцию

Сообщение iskander » 12.10.2021 16:42:24

Для реализации цикла for-in компилятору не нужен метод Reset(), он его никак не вызывает.
Происходит примерно следующее, пусть у вас есть цикл
Код: Выделить всё
...
  for s in c do
    SomehowUse(s);
...

компилятор его заменяет на
Код: Выделить всё
//пусть енумератор это класс
...
var
  e: ...
...
  e := c.GetEnumerator;
  try
    while e.MoveNext do begin
      s := e.Current;
      SomehowUse(s);
    end;
  finally
    e.Free;
  end;
...
iskander
энтузиаст
 
Сообщения: 590
Зарегистрирован: 08.01.2012 18:43:34

Re: Самый простой способ сделать Enumerable коллекцию

Сообщение runewalsh » 12.10.2021 18:43:39

Коллекция должна (точнее, может) реализовывать не IEnumerator, а IEnumerable, отдельный класс итератора нужен в любом случае. Так что разницы с тем, чтобы сделать просто класс и методы, нет.

Коллекции крайне нежелательно быть итератором для самой себя (вместо того, чтобы иметь отдельный класс итератора), потому что пользователь может попросить больше одного:
Код: Выделить всё
for x in myColl do
   for y in myColl do
      if x <> y then ...

А вот итераторы иногда делают итераторами самих себя (т. е. GetEnumerator возвращает self), как в статье, чтобы можно было писать for x in myColl.Reversed без дополнительных прокладок.
Аватара пользователя
runewalsh
энтузиаст
 
Сообщения: 578
Зарегистрирован: 27.04.2010 00:15:25

Re: Самый простой способ сделать Enumerable коллекцию

Сообщение Climber » 13.10.2021 01:24:17

runewalsh писал(а):Коллекция должна (точнее, может) реализовывать не IEnumerator, а IEnumerable
Спасибо за пояснения. Как-то я проглядел, что там разные названия.

Вот что я еще нашел: https://www.freepascal.org/docs-html/re ... 300013.2.5
Можно через оператор сделать - я пока так и сделал. У меня даже заработало 8)
Climber
постоялец
 
Сообщения: 415
Зарегистрирован: 03.06.2007 20:09:57
Откуда: Москва


Вернуться в Free Pascal Compiler

Кто сейчас на конференции

Сейчас этот форум просматривают: Google [Bot] и гости: 28

Рейтинг@Mail.ru