tlist にフィルタリングされた列挙器を実装するより良い方法
-
02-10-2019 - |
質問
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を呼び出す方法をサポートしているのでしょうか?
後で編集:
私は、匿名の方法を使用し、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<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;
解決
delphi for in loopサポートには、次のことが必要です。ドキュメントから)
- 配列、セット、文字列など、コンパイラが認識するプリミティブタイプ
- ienumerableを実装するタイプ
- Delphi Language Guideに記載されているように、GetEnumeratorパターンを実装するタイプ
Generics.collections.pasを見ると、の実装が見つかります TDictionary<TKey,TValue>
3つの列挙者があります TKey
, TValue
, 、 と 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メソッドを匿名で宣言することができます。
他のヒント
Dehlを参照してください( http://code.google.com/p/delphilhlplib/ )。次のようなコードを書くことができます:
for E in List.Where(...).Distinct.Reversed.Take(10).Select(...)... etc.
.netでできるように(もちろん構文はありません)。
あなたのアプローチは大丈夫です。私はより良い方法を知りません。
列挙者ファクトリーは、インターフェイスの代わりにレコードとして実装することもできます。
多分あなたはいくつかのアイデアを得るでしょう ここ.
あなたが追加する場合、あなたは工場とインターフェイスを廃止することができます 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@
私はこのアプローチを使用します... 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;