如何为从 TDictionary 派生的类创建自定义枚举器?

发布于 2024-11-08 15:52:04 字数 1372 浏览 0 评论 0原文

我已经定义了一个从 TDictionary 派生的集合,并且需要定义一个应用附加过滤器的自定义枚举器。

我陷入困境,因为我无法访问 TDictionary FItems 数组(它是私有的),因此我无法定义 MoveNext 方法

您将如何继续在从 TDictionary 派生的类上重新定义过滤枚举器?

这是一个简单的代码来说明我想要做什么:

TMyItem = class(TObject)
public
  IsHidden:Boolean; // The enumerator should not return hidden items
end;
TMyCollection<T:TMyItem> = class(TDictionary<integer,T>)
public
   function GetEnumerator:TMyEnumerator<T>; // A value filtered enumerator
   type
     TMyEnumerator = class(TEnumerator<T>)
     private
       FDictionary: TMyCollection<integer,T>;
       FIndex: Integer;
       function GetCurrent: T;
     protected
       function DoGetCurrent: T; override;
       function DoMoveNext: Boolean; override;
     public
       constructor Create(ADictionary: TMyCollection<integer,T>);
       property Current: T read GetCurrent;
       function MoveNext: Boolean;
     end;
end;

function TMyCollection<T>.TMyEnumerator.MoveNext: Boolean;
begin
// In below code, FIndex is not accessible, so I can't move forward until my filter applies
  while FIndex < Length(FDictionary.FItems) - 1 do   
  begin
    Inc(FIndex);
    if (FDictionary.FItems[FIndex].HashCode <> 0) 
      and not(FDictionary.FItems[FIndex].IsHidden) then // my filter
      Exit(True);
  end;
  Result := False;
end;

I have defined a collection derived from TDictionary, and need to define a custom enumerator that apply an additional filter.

I'm stuck as I can't access the TDictionary FItems array (it is private) so I can't define the MoveNext method

How would you proceed to redefine a filtered enumerator on a class derived from TDictionary?

Here's a simple code to illustrate what I want to do:

TMyItem = class(TObject)
public
  IsHidden:Boolean; // The enumerator should not return hidden items
end;
TMyCollection<T:TMyItem> = class(TDictionary<integer,T>)
public
   function GetEnumerator:TMyEnumerator<T>; // A value filtered enumerator
   type
     TMyEnumerator = class(TEnumerator<T>)
     private
       FDictionary: TMyCollection<integer,T>;
       FIndex: Integer;
       function GetCurrent: T;
     protected
       function DoGetCurrent: T; override;
       function DoMoveNext: Boolean; override;
     public
       constructor Create(ADictionary: TMyCollection<integer,T>);
       property Current: T read GetCurrent;
       function MoveNext: Boolean;
     end;
end;

function TMyCollection<T>.TMyEnumerator.MoveNext: Boolean;
begin
// In below code, FIndex is not accessible, so I can't move forward until my filter applies
  while FIndex < Length(FDictionary.FItems) - 1 do   
  begin
    Inc(FIndex);
    if (FDictionary.FItems[FIndex].HashCode <> 0) 
      and not(FDictionary.FItems[FIndex].IsHidden) then // my filter
      Exit(True);
  end;
  Result := False;
end;

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

醉南桥 2024-11-15 15:52:04

您可以将枚举器基于 TDictionary 的枚举器,因此您实际上不需要访问 FItems。即使您按照 Barry 的建议围绕 TDictionary 编写了一个包装类,这仍然有效。枚举器看起来像这样:

TMyEnumerator = class
protected
  BaseEnumerator: TEnumerator<TPair<Integer, T>>; // using the key and value you used in your sample
public
  function MoveNext:Boolean;
  property Current:T read GetCurrent;
end;

function TMyEnumerator.MoveNext:Boolean;
begin
  Result := BaseEnumerator.MoveNext;
  while Result and (not (YourTestHere)) do // ie: the base enumerator returns everything, reject stuff you don't like
    Result := BaseEnumerator.MoveNext;
end;

function TMyEnumerator.Current: T;
begin
  Result := BaseEnumerator.Current.Value; // Based on your example, it's value you want to extract
end;

这是一个完整的 100 行控制台应用程序,演示了这一点:

program Project23;

{$APPTYPE CONSOLE}

uses
  SysUtils, Generics.Collections;

type

  TMyType = class
  public
    Int: Integer;
    constructor Create(anInteger:Integer);
  end;

  TMyCollection<T:TMyType> = class(TDictionary<integer,T>)
  strict private
    type
      TMyEnumerator = class
      protected
        BaseEnum: TEnumerator<TPair<Integer,T>>;
        function GetCurrent: T;
      public
        constructor Create(aBaseEnum: TEnumerator<TPair<Integer,T>>);
        destructor Destroy;override;

        function MoveNext:Boolean;
        property Current:T read GetCurrent;
      end;
  public
    function GetEnumerator: TMyEnumerator;
  end;

{ TMyCollection<T> }

function TMyCollection<T>.GetEnumerator: TMyEnumerator;
begin
  Result := TMyEnumerator.Create(inherited GetEnumerator);
end;

{ TMyType }

constructor TMyType.Create(anInteger: Integer);
begin
  Int := anInteger;
end;

{ TMyCollection<T>.TMyEnumerator }

constructor TMyCollection<T>.TMyEnumerator.Create(aBaseEnum: TEnumerator<TPair<Integer, T>>);
begin
  BaseEnum := aBaseEnum;
end;

function TMyCollection<T>.TMyEnumerator.GetCurrent: T;
begin
  Result := BaseEnum.Current.Value;
end;

destructor TMyCollection<T>.TMyEnumerator.Destroy;
begin
  BaseEnum.Free;
  inherited;
end;

function TMyCollection<T>.TMyEnumerator.MoveNext:Boolean;
begin
  Result := BaseEnum.MoveNext;
  while Result and ((BaseEnum.Current.Value.Int mod 2) = 1) do
    Result := BaseEnum.MoveNext;
end;

var TMC: TMyCollection<TMyTYpe>;
    V: TMyType;

begin
  try
    TMC := TMyCollection<TMyType>.Create;
    try
      // Fill TMC with some values
      TMC.Add(1, TMyType.Create(1));
      TMC.Add(2, TMyType.Create(2));
      TMC.Add(3, TMyType.Create(3));
      TMC.Add(4, TMyType.Create(4));
      TMC.Add(5, TMyType.Create(5));
      TMC.Add(6, TMyType.Create(6));
      TMC.Add(7, TMyType.Create(7));
      TMC.Add(8, TMyType.Create(8));
      // Filtered-enum
      for V in TMC do
        WriteLn(V.Int);
      ReadLn;
    finally TMC.Free;
    end;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

You can base your Enumerator on TDictionary's enumerator, so you don't actually need access to FItems. This works even if you write a wrapper class around TDictionary as Barry suggests. The enumerator would look like this:

TMyEnumerator = class
protected
  BaseEnumerator: TEnumerator<TPair<Integer, T>>; // using the key and value you used in your sample
public
  function MoveNext:Boolean;
  property Current:T read GetCurrent;
end;

function TMyEnumerator.MoveNext:Boolean;
begin
  Result := BaseEnumerator.MoveNext;
  while Result and (not (YourTestHere)) do // ie: the base enumerator returns everything, reject stuff you don't like
    Result := BaseEnumerator.MoveNext;
end;

function TMyEnumerator.Current: T;
begin
  Result := BaseEnumerator.Current.Value; // Based on your example, it's value you want to extract
end;

And here's a complete, 100 lines console application that demonstrates this:

program Project23;

{$APPTYPE CONSOLE}

uses
  SysUtils, Generics.Collections;

type

  TMyType = class
  public
    Int: Integer;
    constructor Create(anInteger:Integer);
  end;

  TMyCollection<T:TMyType> = class(TDictionary<integer,T>)
  strict private
    type
      TMyEnumerator = class
      protected
        BaseEnum: TEnumerator<TPair<Integer,T>>;
        function GetCurrent: T;
      public
        constructor Create(aBaseEnum: TEnumerator<TPair<Integer,T>>);
        destructor Destroy;override;

        function MoveNext:Boolean;
        property Current:T read GetCurrent;
      end;
  public
    function GetEnumerator: TMyEnumerator;
  end;

{ TMyCollection<T> }

function TMyCollection<T>.GetEnumerator: TMyEnumerator;
begin
  Result := TMyEnumerator.Create(inherited GetEnumerator);
end;

{ TMyType }

constructor TMyType.Create(anInteger: Integer);
begin
  Int := anInteger;
end;

{ TMyCollection<T>.TMyEnumerator }

constructor TMyCollection<T>.TMyEnumerator.Create(aBaseEnum: TEnumerator<TPair<Integer, T>>);
begin
  BaseEnum := aBaseEnum;
end;

function TMyCollection<T>.TMyEnumerator.GetCurrent: T;
begin
  Result := BaseEnum.Current.Value;
end;

destructor TMyCollection<T>.TMyEnumerator.Destroy;
begin
  BaseEnum.Free;
  inherited;
end;

function TMyCollection<T>.TMyEnumerator.MoveNext:Boolean;
begin
  Result := BaseEnum.MoveNext;
  while Result and ((BaseEnum.Current.Value.Int mod 2) = 1) do
    Result := BaseEnum.MoveNext;
end;

var TMC: TMyCollection<TMyTYpe>;
    V: TMyType;

begin
  try
    TMC := TMyCollection<TMyType>.Create;
    try
      // Fill TMC with some values
      TMC.Add(1, TMyType.Create(1));
      TMC.Add(2, TMyType.Create(2));
      TMC.Add(3, TMyType.Create(3));
      TMC.Add(4, TMyType.Create(4));
      TMC.Add(5, TMyType.Create(5));
      TMC.Add(6, TMyType.Create(6));
      TMC.Add(7, TMyType.Create(7));
      TMC.Add(8, TMyType.Create(8));
      // Filtered-enum
      for V in TMC do
        WriteLn(V.Int);
      ReadLn;
    finally TMC.Free;
    end;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.
不奢求什么 2024-11-15 15:52:04

您应该编写一个包装 TDictionary 的类,而不是直接继承它。 TDictionary 可以继承的唯一原因是可以定义 TObjectDictionary 并与其保持多态性。也就是说,通过重写 TDictionary 的唯一正确支持是自定义从字典中删除键和值时发生的情况(因此可能需要释放它们)。

You should write a class that wraps TDictionary rather than inherits from it directly. The only reason TDictionary can be inherited from at all is so that TObjectDictionary could be defined and stay polymorphic with it. That is, the only proper support through overriding TDictionary is to customize what happens when keys and values are removed from the dictionary (so they might need to be freed).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文