【问题标题】:How to create a custom enumerator for a class derived from TDictionary?如何为从 TDictionary 派生的类创建自定义枚举器?
【发布时间】:2011-05-18 11:57:43
【问题描述】:

我已经定义了一个从 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;

【问题讨论】:

    标签: delphi enumerator tdictionary


    【解决方案1】:

    您可以将您的枚举器基于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.
    

    【讨论】:

    • 感谢这个完整的例子,我现在对如何使用枚举器有了更好的理解。我还发现包装枚举器会带来巨大的性能损失(在我的示例应用程序中,使用常规 TDictionary 枚举器进行对象查询需要 2.2 毫秒,使用包装枚举器需要 3.3 毫秒(+50%!),而无需应用任何过滤器)。跨度>
    • @user315561,2.2ms 需要多少时间?现代 CPU 上的 2.2 毫秒是一个非常长的时间,你可能会计算错误;这使得包装枚举器的 3.3 毫秒结果同样错误。
    • 我同意我的数据(脱离上下文提供)不相关。用例是对 75000 个对象的集合的查询,使用简单的相等过滤器按顺序浏览,并将 850 个匹配对象添加到新的结果集合中。我想在这里强调的是枚举器包装的成本损失(在相同的用例上),这是相关的并且很高兴知道。
    • 请注意,如果将TMyCollection&lt;T&gt; 类型的引用分配给TDictionary&lt;Integer,T&gt; 类型的位置,并且随后通过该位置调用GetEnumerator,则此方法将中断,因为它将调用TDictionary&lt;Integer,T&gt; 版本的GetEnumerator。这只是说明为什么不包装类型是个坏主意的一种表现。
    【解决方案2】:

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

    【讨论】:

    • 谢谢巴里,很好的推荐。另一个优点是使我的集合减少与字典选择的耦合,从而更容易在可用时用更高性能的字典替换它
    猜你喜欢
    • 1970-01-01
    • 2017-05-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-16
    • 2021-07-28
    • 1970-01-01
    相关资源
    最近更新 更多