2011-05-18 23 views
9

我已经定义了一个从TDictionary派生的集合,并且需要定义一个应用附加过滤器的自定义枚举器。如何为从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; 

回答

5

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

感谢这个完整的示例,我现在更好地理解如何使用枚举器。我还发现,包装枚举器有一个巨大的性能损失(在我的示例应用程序中,对象查询需要常规TDictionary枚举器2.2ms,包装枚举器3.3ms(+ 50%!),而不应用任何过滤器)。 – user315561 2011-05-19 07:24:21

+0

@ user315561,什么需要2.2ms?在现代CPU上2.2毫秒是一个可怕的长时间,你可能错过了一些东西;并且这使得包装的枚举器的3.3ms结果同样是错误的。 – 2011-05-19 07:45:16

+0

我同意我的数字在背景下提供,并不相关。用例是对75000个对象集合的查询,按顺序浏览一个简单的相等过滤器,并将850个匹配对象添加到新的结果集合中。我想在这里强调的是枚举器封装的成本惩罚(在相同的用例中),这是相关并且很好知道的。 – user315561 2011-05-19 10:35:44

4

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

+1

感谢巴里,很好的建议。另一个好处是可以使我的集合更少地与字典选择耦合,从而更容易用更高性能的字典替换它 – user315561 2011-05-19 07:19:52