2010-07-05 103 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

相关问题