【问题标题】:How to expose the built in enumerator for a private static array field in Delphi?如何在 Delphi 中公开私有静态数组字段的内置枚举器?
【发布时间】:2014-01-01 00:01:18
【问题描述】:

我正在尝试为私有静态数组公开 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;

【问题讨论】:

    标签: arrays delphi delphi-xe2 enumerator static-array


    【解决方案1】:

    请注意,我可以编写如下所示的自定义模拟器。但我试图避免这种情况并暴露内置的。

    你不能。没有表示数组枚举数的类型。当您在数组元素上编写 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: 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 移动 eax,[$00407810] 004060E6 8BD7 mov edx,edi 004060E8 E823DCFFFF 调用@Write0Long 004060ED E8FEDEFFFF 调用@WriteLn 004060F2 E869CCFFFF 调用@_IOTest 004060F7 43 公司 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 调用@ReadLn 00406107 E854CCFFFF 调用@_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;
    

    【讨论】:

      【解决方案2】:

      正如 David 所指出的,数组没有内置的枚举器类型,其实现本质上是伪装简单循环的语法糖。

      如果您的 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 数组,枚举值之间有(可索引的)空格,因此这似乎不太可能最终有用。无论如何,由于这不是您问题的一部分,因此上述内容就足够了。

      【讨论】:

      • 这并没有解决所提出的问题
      • @DavidHeffernan 你是什么意思?您已经指出,所问的问题没有答案,因为它试图公开的枚举器根本不存在。如果我们认为问题的意图是寻求一种方法,使基类在给定的静态数组字段上隐式枚举,那么我认为这至少回答了问题的那个方面。
      • 问题中的代码已经实现了与您的代码相同的效果。但问题明确询问如何公开内置数组枚举器。
      • @DavidHeffernan 问题中的代码是在我回答这个问题时添加的。最初的问题也是,“我怎样才能公开 AN 枚举器......” - 它在后来的编辑中被重新措辞。你想看到这个答案改变吗?删除了吗?
      • 我没有看到这个问题的原始版本,所以现在我知道你来自哪里了。尽管如此,即使这样说:Delphi 本身允许直接枚举静态数组(见下文),所以我怀疑 Delphi 在后台为静态数组创建了一个枚举器,我希望我能够创建和公开GetEnumerator 方法中的相同枚举数。
      猜你喜欢
      • 1970-01-01
      • 2017-05-12
      • 1970-01-01
      • 2020-02-16
      • 2014-11-07
      • 1970-01-01
      • 2012-03-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多