Question

I am trying to expose the build in TEnumerator for a private static array.

Delphi itself allows to enumerate a static array directly (see below) so I suspect that Delphi creates an enumerator in the background for the static array and I am hoping that I will be able to create and expose the same enumerator in GetEnumerator method.

(I am using 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.

Note that I can write a custom emulator like below. But I am trying to avoid this and expose the built in one.

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;
Was it helpful?

Solution

Note that I can write a custom emulator like below. But I am trying to avoid this and expose the built in one.

You cannot. There is no type that represents the enumerator for an array. When you write a for..in loop over the elements of an array, the compiler deals with that by inlining a classic for loop.

Consider this program:

type
  TMyEnum = (enum1, enum2, enum3);

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

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

And the code that is generated:

Project1.dpr.13: for i in arr do
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               inc ebx
Project1.dpr.13: for i in arr do
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

Frankly, the best you can do is very similar to what you already have. The problem with what you already have is the heap allocation. Write your enumerator using a record rather than a class, like this:

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;

And then your GetEnumerator is implemented like so:

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

OTHER TIPS

As David noted, there is no built-in enumerator type for arrays and the implementation is essentially syntactic sugar disguising a simple loop.

To suggest an alternative way to make your class enumerable, if your TMyEnum is contiguous (which it seems to be), and if you're not necessarily looking for a generic implementation (which isn't clear) :

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;

implement as :

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;

example :

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.

If the enum is not contiguous, ie :

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

then obviously the implementation doesn't work. It also creates static TMyItems arrays with (indexable) empty spaces between enum values so it seems rather unlikely that this would end up being useful anyway. In any case, since this isn't part of your question, the above should be sufficient.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top