Как выставить встроенный перечислитель для поля частного статического массива в Delphi?

Я пытаюсь выставить сборку в TEnumerator для частного статического массива.

Сам Delphi позволяет напрямую перечислять статический массив (см. Ниже), поэтому я подозреваю, что Delphi создает перечислитель в фоновом режиме для статического массива, и я надеюсь, что смогу создать и выставить тот же перечислитель в методе GetEnumerator.

(Я использую Delphi XE2).

program Project6;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  System.Generics.Collections;

type
  TMyEnum = (meA, meB);

  TMyClass = class
  private
    FItems: array[TMyEnum] of Integer;
  protected
  public
    function GetEnumerator: TEnumerator<Integer>;
  end;

{ TMyClass }

function TMyClass.GetEnumerator: TEnumerator<Integer>;
begin
  // What is the simplies way of creating this enumerator?
end;

var
  myObj: TMyClass;
  i: Integer;

begin
  myObj := TMyClass.Create;
  try
    // This works but only in the same unit
    for i in myObj.FItems do
      WriteLn(i);

    for i in myObj do
      WriteLn(i);

  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  ReadLn;
end.

Обратите внимание, что я могу написать собственный эмулятор, как показано ниже. Но я пытаюсь избежать этого и разоблачить встроенное.

TStaticArrayEnumerator<T> = class(TEnumerator<T>)
  private
    FCurrent: Pointer;
    FElementAfterLast: Pointer;
  protected
    function DoGetCurrent: T; override;
    function DoMoveNext: Boolean; override;
  public
    constructor Create(aArray: Pointer; aCount: Integer);
  end;

{ TStaticArrayEnumerator<T> }

constructor TStaticArrayEnumerator<T>.Create(aArray: Pointer; aCount: Integer);
begin
  // need to point Current before the first element (see comment in DoMoveNext)
  FCurrent := Pointer(NativeInt(aArray) - SizeOf(T));
  FElementSize := aElementSize;
  FElementAfterLast := Pointer(NativeInt(aArray) + aCount * SizeOf(T))
end;

function TStaticArrayEnumerator<T>.DoGetCurrent: T;
begin
  Result := T(FCurrent^);
end;

function TStaticArrayEnumerator<T>.DoMoveNext: Boolean;
begin
  // This method gets called before DoGetCurrent gets called the first time
  FCurrent := Pointer(NativeInt(FCurrent) + SizeOf(T));

  Result := not (FCurrent = FElementAfterLast);
end;

2 ответа

Решение

Обратите внимание, что я могу написать собственный эмулятор, как показано ниже. Но я пытаюсь избежать этого и разоблачить встроенное.

Ты не можешь. Нет типа, который представляет перечислитель для массива. Когда вы пишете цикл for..in над элементами массива, компилятор справляется с этим, вставляя классический цикл for.

Рассмотрим эту программу:

type
  TMyEnum = (enum1, enum2, enum3);

var
  arr: array [TMyEnum] of Integer;
  i: Integer;

begin
  for i in arr do
    Writeln(i);
  Readln;
end.

И код, который генерируется:

Project1.dpr.13: для меня в об
004060D7 BE9CAB4000 MOV ESI,$0040ab9c
004060DC 33DB             xor ebx,ebx
004060DE 8B3C9E           mov edi,[esi+ebx*4]
Project1.dpr.14: Writeln(i);
004060E1 A110784000       mov eax,[$00407810]
004060E6 8BD7             mov edx,edi
004060E8 E823DCFFFF       call @Write0Long
004060ED E8FEDEFFFF       call @WriteLn
004060F2 E869CCFFFF       call @_IOTest
004060F7 43 вкл. Отлив
Project1.dpr.13: для меня в об
004060F8 83FB03           cmp ebx,$03
004060FB 75E1             jnz $004060de
Project1.dpr.15: Readln;
004060FD A114784000       mov eax,[$00407814]
00406102 E8E5D7FFFF       call @ReadLn
00406107 E854CCFFFF       call @_IOTest

Честно говоря, лучшее, что вы можете сделать, очень похоже на то, что у вас уже есть. Проблема с тем, что у вас уже есть, - это распределение кучи. Напишите свой перечислитель, используя запись, а не класс, например так:

type
  TArrayEnumerator<T> = record
  strict private
    type
      P = ^T;
  strict private
    FArr: P;
    FIndex: Integer;
    FCount: Integer;
  public
    class function Initialize(const Arr: array of T): TArrayEnumerator<T>; static;
    function GetCurrent: T;
    function MoveNext: Boolean;
    property Current: T read GetCurrent;
  end;

class function TArrayEnumerator<T>.Initialize(const Arr: array of T): TArrayEnumerator<T>;
begin
  Result.FArr := @Arr[low(Arr)];
  Result.FIndex := -1;
  Result.FCount := Length(Arr);
end;

function TArrayEnumerator<T>.MoveNext: Boolean;
begin
  Result := FIndex < FCount-1;
  if Result then
    inc(FIndex);
end;

function TArrayEnumerator<T>.GetCurrent: T;
var
  Ptr: P;
begin
  Ptr := FArr;
  inc(Ptr, FIndex);
  Result := Ptr^;
end;

А потом твой GetEnumerator реализован так:

function TMyClass.GetEnumerator: TArrayEnumerator<Integer>;
begin
  Result := TArrayEnumerator<Integer>.Initialize(FItems);
end;

Как отметил Дэвид, встроенного типа перечислителя для массивов не существует, а реализация по сути является синтаксическим сахаром, маскирующим простой цикл.

Предложить альтернативный способ сделать ваш класс перечислимым, если ваш TMyEnum является смежным (что, кажется, так), и если вы не обязательно ищете универсальную реализацию (что не ясно):

type
  TMyEnum = (meA, meB);
  TMyItems = array[TMyEnum] of Integer;

  TMyItemsEnum = class
  private
    FGotFirst : boolean;
    FOwner: TMyItems;
    FCurrent : TMyEnum;
  public
    constructor Create(owner: TMyItems);
    function GetCurrent: Integer;
    function MoveNext: boolean;
    property Current: Integer read GetCurrent;
  end;

  TMyClass = class(TObject)
  private
    FItems: TMyItems;
  public
    function GetEnumerator : TMyItemsEnum;
  end;

реализовать как:

constructor TMyItemsEnum.Create(owner: TMyItems);
begin
  FOwner := owner;
  FGotFirst := false;
  FCurrent := TMyEnum(Low(TMyEnum));
end;

function TMyItemsEnum.GetCurrent: Integer;
begin
  Result := FOwner[FCurrent];
end;

function TMyItemsEnum.MoveNext: boolean;
begin
  Result := false;
  if not FGotFirst then begin
    FGotFirst := true;
    Result := true;
  end else begin
    if Ord(FCurrent) < Ord(High(TMyEnum)) then begin
      FCurrent := TMyEnum(Succ(FCurrent));
      Result := true;
    end;
  end;
end;

function TMyClass.GetEnumerator : TMyItemsEnum;
begin
  result := TMyItemsEnum.Create(FItems);
end;

пример:

var
  myObj: TMyClass;
  i: Integer;

begin
  myObj := TMyClass.Create;
  myObj.FItems[meA] := 123;
  myObj.FItems[meB] := 456;

  for i in myObj do WriteLn(i);
end.

Если перечисление не является смежным, то есть:

 TMyEnum = (meA = 3, meB = 17);

тогда, очевидно, реализация не работает. Это также создает статический TMyItems массивы с (индексируемыми) пустыми пробелами между значениями enum, поэтому кажется маловероятным, что это все равно окажется полезным. В любом случае, поскольку это не является частью вашего вопроса, вышеприведенного должно быть достаточно.

Другие вопросы по тегам