在 TList上实现过滤枚举器的更好方法
使用 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
请参阅 DeHL (http://code.google.com/p/delphilhlplib/)。您可以编写如下所示的代码:
就像您在 .NET 中所做的那样(当然没有语法 linq)。
See DeHL ( http://code.google.com/p/delphilhlplib/ ). You can write code that looks like this:
Just like you can do in .NET (no syntax linq of course).
你的做法没问题。我不知道还有什么更好的办法。
枚举器工厂也可以实现为记录而不是接口。
也许您会在这里得到一些想法< /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.
Delphi For 循环支持需要以下之一: (来自文档)
识别,例如数组、集合或
字符串
IEnumerable
文档中的 GetEnumerator 模式
如果您查看Generics.Collections.pas,
您会发现
TDictionary
的实现,其中包含TKey
、三个枚举器>TValue
和TPair
类型。 Embarcadero 表明他们使用了详细的实现。您可以这样做:
这将允许您匿名声明 Current 和 MoveNext 方法。
Delphi For in loop support requires on of the following: (From the Docs)
recognizes, such as arrays, sets or
strings
IEnumerable
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 forTKey
,TValue
, andTPair<TKey,TValue>
types. Embarcadero shows that they have used verbose implementation.You could do something like this:
This would allow you declare your Current and MoveNext methods anonymously.
如果您向枚举器添加 GetEnumerator() 函数,则可以取消工厂和接口,如下所示:
并且只需返回 self:
并且 Delphi 将方便地为您清理实例,就像它可以执行任何其他枚举器:
然后您可以扩展枚举器以使用匿名方法,您可以在构造函数中传递该方法:
像这样调用它:
然后您甚至可以将其设为泛型,但我不会在这里这样做,我的答案已经足够长了。
氮@
You can do away with the factory and the interface if you add a
GetEnumerator()
function to your enumerator, like this:and just return self:
and Delphi will conveniently clean up your instance for you, just like it does any other enumerator:
You can then extend your enumerator to use an anonymous method, which you can pass in the constructor:
call it like this:
Then you could even make it a generic, but I wont do that here, my answer is long enough as it is.
N@
我使用这种方法...其中 AProc 执行过滤器测试。
I use this approach...where the AProc performs the filter test.