2011-05-18 22 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