在 TList上实现过滤枚举器的更好方法

发布于 2024-09-08 08:23:48 字数 3603 浏览 5 评论 0原文

使用 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 的枚举器来显示这个概念的力量):

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;

在任何类 d 使用这个新的枚举器:

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

Using Delphi 2010, let's say I've got a class declared like this:

TMyList = TList<TMyObject>

For this list Delphi kindly provides us with an enumerator, so we can write this:

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

The trouble is, I'd like to write this:

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

That is, I want the ability to provide multiple enumerators for the same list, using some criteria. Unfortunately the implementation of for X in Z requires the presence of a function Z.GetEnumerator, with no parameters, that returns the given enumerator! To circumvent this problem I'm defining an interface that implements the "GetEnumerator" function, then I implement a class that implements the interface and finally I write a function on TMyList that returns the interface! And I'm returning an interface because I don't want to be bothered with manually freeing the very simple class... Any way, this requires a LOT of typing. Here's how this would look like:

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;

Using this I can finally write:

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

Do you know a better way of doing this? Maybe Delphi does support a way of calling GetEnumerator with a parameter directly?

Later Edit:

I decided to use Robert Love's idea of implementing the enumerator using anonymous methods and using gabr's "record" factory to save yet an other class. This allows me to create a brand new enumerator, complete with code, using just a few lines of code in a function, no new class declaration required.

Here's how my generic enumerator is declared, in a library unit:

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;

And here's a way to use it. On any class I can add a function like this (and I'm intentionally creating an enumerator that doesn't use a List<T> to show the power of this concept):

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;

And here's how I'd use this new enumerator:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

暖树树初阳… 2024-09-15 08:23:48

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

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

就像您在 .NET 中所做的那样(当然没有语法 linq)。

See DeHL ( http://code.google.com/p/delphilhlplib/ ). You can write code that looks like this:

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

Just like you can do in .NET (no syntax linq of course).

濫情▎り 2024-09-15 08:23:48

你的做法没问题。我不知道还有什么更好的办法。

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

也许您会在这里得到一些想法< /a>.

You approach is fine. I don't know of any better way.

Enumerator factory can also be implemented as a record instead of an interface.

Maybe you'll get some ideas here.

梦归所梦 2024-09-15 08:23:48

Delphi For 循环支持需要以下之一: (来自文档

  • 编译器的基本类型
    识别,例如数组、集合或
    字符串
  • 实现的 类型
    IEnumerable
  • 实现的 类型
    文档中的 GetEnumerator 模式
    如果您查看Generics.Collections.pas,

您会发现 TDictionary 的实现,其中包含 TKey三个枚举器>TValueTPair 类型。 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 方法。

Delphi For in loop support requires on of the following: (From the Docs)

  • Primitive types that the compiler
    recognizes, such as arrays, sets or
    strings
  • Types that implement
    IEnumerable
  • Types that implement the
    GetEnumerator pattern as documented
    in the Delphi Language Guide

If you look at Generics.Collections.pas you will find the implementation for TDictionary<TKey,TValue> where it has three enumerators for TKey, TValue, and TPair<TKey,TValue> types. Embarcadero shows that they have used verbose implementation.

You could do something like this:

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.

This would allow you declare your Current and MoveNext methods anonymously.

请恋爱 2024-09-15 08:23:48

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

并且只需返回 self:

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;

然后您甚至可以将其设为泛型,但我不会在这里这样做,我的答案已经足够长了。

氮@

You can do away with the factory and the interface if you add a GetEnumerator() function to your enumerator, like this:

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;

and just return self:

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

and Delphi will conveniently clean up your instance for you, just like it does any other enumerator:

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

You can then extend your enumerator to use an anonymous method, which you can pass in the constructor:

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;

call it like this:

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;

Then you could even make it a generic, but I wont do that here, my answer is long enough as it is.

N@

许仙没带伞 2024-09-15 08:23:48

我使用这种方法...其中 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;

I use this approach...where the AProc performs the filter test.

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;
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文