2010-07-05 35 views
10

用Delphi 2010過濾枚舉,讓我們說我有聲明如下一類:更好的方式來實現對從TList <TMyObject>

TMyList = TList<TMyObject> 

對於這個列表德爾福好心爲我們提供了一個枚舉,所以我們可以這樣寫這樣的:

var L:TMyList; 
    E:TMyObject; 
begin 
    for E in L do ; 
end; 

麻煩的是,我想這樣寫:

var L:TMyList; 
    E:TMyObject; 
begin 
    for E in L.GetEnumerator('123') do ; 
end; 

也就是說,我希望能夠使用一些標準爲同一個列表提供多個枚舉器。不幸的是,for X in Z的實現需要存在一個函數Z.GetEnumerator,沒有參數,它返回給定的枚舉器!爲了規避這個問題,我定義了一個實現「GetEnumerator」函數的接口,然後我實現了一個實現接口的類,最後我在TMyList上寫了一個返回接口的函數!而且我正在返回一個界面,因爲我不想爲手動釋放這個非常簡單的類而煩惱......不管怎樣,這需要大量的輸入。這裏是如何做到這一點的樣子:

TMyList = class(TList<TMyObject>) 
protected 

    // Simple enumerator; Gets access to the "root" list 
    TSimpleEnumerator = class 
    protected 
    public 
    constructor Create(aList:TList<TMyObject>; FilterValue:Integer); 

    function MoveNext:Boolean; // This is where filtering happens 
    property Current:TTipElement; 
    end; 

    // Interface that will create the TSimpleEnumerator. Want this 
    // to be an interface so it will free itself. 
    ISimpleEnumeratorFactory = interface 
    function GetEnumerator:TSimpleEnumerator; 
    end; 

    // Class that implements the ISimpleEnumeratorFactory 
    TSimpleEnumeratorFactory = class(TInterfacedObject, ISimpleEnumeratorFactory) 
    function GetEnumerator:TSimpleEnumerator; 
    end; 

public 
    function FilteredEnum(X:Integer):ISimpleEnumeratorFactory; 
end; 

使用這個我終於可以寫:

​​

你知不知道這樣做的更好的辦法?也許德爾福支持一種直接調用GetEnumerator的方法?

後來編輯:

我決定用使用匿名方法實現枚舉和使用賈布爾的「記錄」工廠還保存其他類的羅伯特·愛的想法。這使我可以創建一個全新的枚舉器,並使用代碼完成,只需在函數中使用幾行代碼,不需要新的類聲明。

這裏是我的通用枚舉的聲明方式,在庫單元:

TEnumGenericMoveNext<T> = reference to function: Boolean; 
TEnumGenericCurrent<T> = reference to function: T; 

TEnumGenericAnonim<T> = class 
protected 
    FEnumGenericMoveNext:TEnumGenericMoveNext<T>; 
    FEnumGenericCurrent:TEnumGenericCurrent<T>; 
    function GetCurrent:T; 
public 
    constructor Create(EnumGenericMoveNext:TEnumGenericMoveNext<T>; EnumGenericCurrent:TEnumGenericCurrent<T>); 

    function MoveNext:Boolean; 
    property Current:T read GetCurrent; 
end; 

TGenericAnonEnumFactory<T> = record 
public 
    FEnumGenericMoveNext:TEnumGenericMoveNext<T>; 
    FEnumGenericCurrent:TEnumGenericCurrent<T>; 
    constructor Create(EnumGenericMoveNext:TEnumGenericMoveNext<T>; EnumGenericCurrent:TEnumGenericCurrent<T>); 
    function GetEnumerator:TEnumGenericAnonim<T>; 
end; 

下面是使用它的方式。在任何類,我可以添加這樣的函數(我故意創建不使用List<T>顯示此概念的電力枚舉):

type Form1 = class(TForm) 
protected 
    function Numbers(From, To:Integer):TGenericAnonEnumFactory<Integer>; 
end; 

// This is all that's needed to implement an enumerator! 
function Form1.Numbers(From, To:Integer):TGenericAnonEnumFactory<Integer>; 
var Current:Integer; 
begin 
    Current := From - 1; 
    Result := TGenericAnonEnumFactory<Integer>.Create(
    // This is the MoveNext implementation 
    function :Boolean 
    begin 
     Inc(Current); 
     Result := Current <= To; 
    end 
    , 
    // This is the GetCurrent implementation 
    function :Integer 
    begin 
     Result := Current; 
    end 
); 
end; 

這裏就是我想用這個新統計員:

procedure Form1.Button1Click(Sender: TObject); 
var N:Integer; 
begin 
    for N in Numbers(3,10) do 
    Memo1.Lines.Add(IntToStr(N)); 
end; 
+0

感謝這一點,現在實現對類的枚舉器支持變得更容易了。如果你想使用'for obj in list do'之類的東西,那麼只需像這樣聲明GetEnumerator():'function GetEnumerator:TEnumGenericAnonim ',並且在實現中只需在Create語句的最後添加'.GetEnumerator'即可。 – Sharken 2012-02-20 07:53:52

回答

4

的Delphi對於循環支持需要對下列組成:(From the Docs

  • 原始類型,編譯器 識別,諸如數組,集合或 串實現 IEnumerable的
  • 類型
  • 實現 GetEnumerator模式的類型在Delphi語言指南中記錄爲

如果你看看Generics.Collections.pas你會發現TDictionary<TKey,TValue>實施它有TKeyTValue 3名普查員和TPair<TKey,TValue>類型。 Embarcadero表明他們已經使用了詳細的實現。

你可以做這樣的事情:

unit Generics.AnonEnum; 
interface 
uses 
SysUtils, 
Generics.Defaults, 
Generics.Collections; 

type 

    TAnonEnumerator<T> = class(TEnumerator<T>) 
    protected 
    FGetCurrent : TFunc<TAnonEnumerator<T>,T>; 
    FMoveNext : TFunc<TAnonEnumerator<T>,Boolean>; 
    function DoGetCurrent: T; override; 
    function DoMoveNext: Boolean; override; 
    public 
    Constructor Create(aGetCurrent : TFunc<TAnonEnumerator<T>,T>; 
         aMoveNext : TFunc<TAnonEnumerator<T>,Boolean>); 
    end; 

    TAnonEnumerable<T> = class(TEnumerable<T>) 
    protected 
    FGetCurrent : TFunc<TAnonEnumerator<T>,T>; 
    FMoveNext : TFunc<TAnonEnumerator<T>,Boolean>; 
    function DoGetEnumerator: TEnumerator<T>; override; 
    public 
    Constructor Create(aGetCurrent : TFunc<TAnonEnumerator<T>,T>; 
         aMoveNext : TFunc<TAnonEnumerator<T>,Boolean>); 
    end; 

implementation 

{ TEnumerable<T> } 

constructor TAnonEnumerable<T>.Create(aGetCurrent: TFunc<TAnonEnumerator<T>, T>; 
    aMoveNext: TFunc<TAnonEnumerator<T>, Boolean>); 
begin 
    FGetCurrent := aGetCurrent; 
    FMoveNext := aMoveNext; 
end; 

function TAnonEnumerable<T>.DoGetEnumerator: TEnumerator<T>; 
begin 
result := TAnonEnumerator<T>.Create(FGetCurrent,FMoveNext); 
end; 


{ TAnonEnumerator<T> } 

constructor TAnonEnumerator<T>.Create(aGetCurrent: TFunc<TAnonEnumerator<T>, T>; 
    aMoveNext: TFunc<TAnonEnumerator<T>, Boolean>); 
begin 
    FGetCurrent := aGetCurrent; 
    FMoveNext := aMoveNext; 
end; 

function TAnonEnumerator<T>.DoGetCurrent: T; 
begin 
    result := FGetCurrent(self); 
end; 

function TAnonEnumerator<T>.DoMoveNext: Boolean; 
begin 
result := FMoveNext(Self); 
end; 

end. 

這將允許你匿名聲明你現在和MoveNext方法。

+1

使用匿名方法來實現枚舉器的想法絕對精彩,它使我無法創建無數類來實現不同的枚舉器。所有枚舉器的代碼通常非常簡單,只需幾行代碼即可實現。謝謝。 – 2010-07-06 05:28:46

1

我使用這種方法...其中AProc執行過濾器測試。

TForEachDataItemProc = reference to procedure (ADataItem: TDataItem; var AFinished: boolean); 

procedure TDataItems.ForEachDataItem(AProc: TForEachDataItemProc); 
var 
    AFinished: Boolean; 
    ADataItem: TDataItem; 
begin 
    AFinished:= False; 
    for ADataItem in FItems.Values do 
    begin 
    AProc(ADataItem, AFinished); 
    if AFinished then 
     Break; 
    end; 
end; 
+0

感謝尼日,但這不能回答我的問題。我詢問了實現枚舉器的方法,因爲我想使用「for E in L」循環。 – 2010-07-05 10:04:12

+0

這是一個好主意(雖然與問題不完全相關),實在不值得降低價值。 – gabr 2010-07-05 13:29:40

8

參見DeHL(http://code.google.com/p/delphilhlplib/)。你可以編寫如下代碼:

for E in List.Where(...).Distinct.Reversed.Take(10).Select(...)... etc. 

就像你可以在.NET中做的那樣(當然沒有語法linq)。

+0

感謝您的鏈接。即使我最終最終使用DeHL,我也會贊成Robert Love的回答,因爲它顯示了一種語言功能,可以讓我自己實現這一點。 – 2010-07-06 05:32:23

+0

當然。如果你不想投資於某種事物,DeHL會爲你節省時間。如果你更喜歡編寫自己的輕量級實現,那麼Robert的答案就是這樣。 – alex 2010-07-07 05:18:09

6

你的做法很好。我不知道有什麼更好的辦法。

枚舉器工廠也可以實現爲記錄而不是接口。

也許你會得到一些想法here

+0

使用記錄而不是界面可以節省我另一個「類」。謝謝。 – 2010-07-06 05:33:14

3

可以與工廠和界面做的路程,如果你添加一個GetEnumerator()功能,你的枚舉,像這樣:

TFilteredEnum = class 
public 
    constructor Create(AList:TList<TMyObject>; AFilterValue:Integer); 

    function GetEnumerator: TFilteredEnum; 

    function MoveNext:Boolean; // This is where filtering happens 
    property Current: TMyObject; 
end; 

,就回到自我:

function TFilteredEnum.GetEnumerator: TSimpleEnumerator; 
begin 
    result := Self; 
end; 

和德爾福將方便爲您清理您的實例,就像其他任何枚舉器一樣:

var 
    L: TMyList; 
    E: TMyObject; 
begin 
    for E in TFilteredEnum.Create(L, 7) do ; 
end; 

然後,您可以擴展您的枚舉使用匿名方法,你可以在構造函數中傳遞:

TFilterFunction = reference to function (AObject: TMyObject): boolean; 

TFilteredEnum = class 
private 
    FFilterFunction: TFilterFunction; 
public 
    constructor Create(AList:TList<TMyObject>; AFilterFunction: TFilterFunction); 

    ... 
end; 

... 

function TFilteredEnum.MoveNext: boolean; 
begin 
    if FIndex >= FList.Count then 
    Exit(False); 
    inc(FIndex); 
    while (FIndex < FList.Count) and not FFilterFunction(FList[FIndex]) do 
    inc(FIndex); 
    result := FIndex < FList.Count; 
end; 

調用它像這樣:

var 
    L:TMyList; 
    E:TMyObject; 
begin 
    for E in TFilteredEnum.Create(L, function (AObject: TMyObject): boolean 
            begin 
            result := AObject.Value = 7; 
            end; 
           ) do 
    begin 
    //do stuff here 
    end 
end; 

然後你甚至可以使一個普通的,但我不會這樣做,我的答案足夠長。

N @

+0

好主意! (填充) – gabr 2010-07-06 11:12:01

相關問題