【问题标题】:Better way to implement filtered enumerator on TList<TMyObject>在 TList<TMyObject> 上实现过滤枚举器的更好方法
【发布时间】:2011-03-11 19:16:24
【问题描述】:

使用 Delphi 2010,假设我有一个这样声明的类:

TMyList = TList<TMyObject>

对于这个列表,Delphi 为我们提供了一个枚举器,所以我们可以这样写:

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;

用这个我终于可以写了:

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

您知道更好的方法吗?也许Delphi确实支持直接带参数调用GetEnumerator的方式?

后期编辑:

我决定使用 Robert Love 的想法,即使用匿名方法实现枚举器并使用 gabr 的“记录”工厂来保存另一个类。这允许我创建一个全新的枚举器,包含代码,在函数中只使用几行代码,不需要新的类声明。

这是我的通用枚举器在库单元中的声明方式:

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&lt;T&gt; 的枚举器来展示这个概念的强大功能):

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;

【问题讨论】:

  • 感谢您,现在实现对类的枚举器支持变得容易多了。如果你想使用'for obj in list do'之类的东西,那么只需像这样声明GetEnumerator():'function GetEnumerator : TEnumGenericAnonim' 并在实现中添加'.GetEnumerator'在创建的最后声明。

标签: delphi filter delphi-2010 foreach enumerator


【解决方案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;

【讨论】:

  • 感谢 Nige,但这并不能回答我的问题。我询问了实现枚举器的方法,因为我想使用“for E in L”循环。
  • 这是个好主意(尽管与问题不完全相关),真的不值得反对。
【解决方案2】:

请参阅 DeHL (http://code.google.com/p/delphilhlplib/)。您可以编写如下所示的代码:

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

就像你可以在 .NET 中做的一样(当然没有语法 linq)。

【讨论】:

  • 感谢您的链接。即使我最终最终使用了 DeHL,我也会赞成 Robert Love 的回答,因为它显示了一种允许我自己实现的语言功能。
  • 当然。如果您不想投资这类东西,DeHL 可以为您节省时间。如果您更喜欢编写自己的轻量级实现,那么 Robert 的答案就是。
【解决方案3】:

Delphi For in 循环支持需要以下之一:(From the Docs)

  • 编译器的原始类型 识别,例如数组、集合或 字符串
  • 实现的类型 IEnumerable
  • 实现的类型 记录的 GetEnumerator 模式 在 Delphi 语言指南中

如果您查看 Generics.Collections.pas,您会发现 TDictionary&lt;TKey,TValue&gt; 的实现,它有三个用于 TKey、TValue 和 TPair&lt;TKey,TValue&gt; 类型的枚举器。 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.

这将允许您匿名声明 Current 和 MoveNext 方法。

【讨论】:

  • 使用匿名方法实现枚举器的想法绝对是绝妙的,它让我免于创建无数类来实现不同的枚举器。毕竟枚举器的代码通常非常简单,只需几行代码即可实现。谢谢。
【解决方案4】:

你的方法很好。我不知道有什么更好的方法。

枚举器工厂也可以实现为记录而不是接口。

也许你会得到一些想法here。

【讨论】:

  • 使用记录而不是界面为我节省了另一个“类”。谢谢。
【解决方案5】:

如果您向枚举器添加 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;

Delphi 会方便地为您清理您的实例,就像它对任何其他枚举器所做的一样:

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@

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-11-22
    • 2021-02-02
    • 2019-03-25
    • 1970-01-01
    • 1970-01-01
    • 2019-12-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多