Une meilleure façon de mettre en œuvre Énumérer du filtre sur la liste
-
02-10-2019 - |
Question
Utilisation de Delphi 2010, disons que j'ai une classe déclarée comme ceci:
TMyList = TList<TMyObject>
Pour cette liste Delphi nous fournit aimablement avec un recenseur, donc nous pouvons écrire ceci:
var L:TMyList;
E:TMyObject;
begin
for E in L do ;
end;
Le problème est, je voudrais écrire ceci:
var L:TMyList;
E:TMyObject;
begin
for E in L.GetEnumerator('123') do ;
end;
C'est, je veux la capacité de fournir plusieurs agents recenseurs pour la même liste, en utilisant certains critères. Malheureusement, la mise en œuvre de for X in Z
nécessite la présence d'une Z.GetEnumerator
fonction, sans paramètre, qui renvoie le recenseur donné! Pour contourner ce problème, je suis la définition d'une interface qui implémente la fonction « GetEnumerator », alors j'implémenter une classe qui implémente l'interface et, enfin, je vous écris une fonction sur TMyList qui renvoie l'interface! Et je retourne une interface parce que je ne veux pas être dérangé par la libération manuellement la classe très simple ... De toute façon, cela nécessite beaucoup de dactylographie. Voici comment cela ressemblerait à ceci:
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;
L'utilisation de ce que je peux enfin écrire:
var L:TMyList;
E:TMyObject;
begin
for E in L.FilteredEnum(7) do ;
end;
Savez-vous une meilleure façon de le faire? Peut-être que Delphi supporte une façon d'appeler GetEnumerator avec un paramètre directement?
Modifier plus tard:
Je décidé d'utiliser l'idée de Robert Amour de mise en œuvre du recenseur en utilisant des méthodes anonymes et utiliser l'usine « record » de Gabr pour sauver encore une autre classe. Cela me permet de créer une nouvelle marque recenseur, avec le code, en utilisant seulement quelques lignes de code dans une fonction, aucune nouvelle déclaration de classe requise.
Voici comment mon recenseur générique est déclaré, dans une unité de bibliothèque:
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;
Et voici une façon de l'utiliser. Sur une classe que je peux ajouter une fonction comme celui-ci (et je crée intentionnellement un recenseur qui n'utilise pas List<T>
pour montrer la puissance de ce 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;
Et voici comment j'utiliser cette nouvelle recenseur:
procedure Form1.Button1Click(Sender: TObject);
var N:Integer;
begin
for N in Numbers(3,10) do
Memo1.Lines.Add(IntToStr(N));
end;
La solution
Delphi Pour l'appui de la boucle nécessite une des options suivantes: ( des Docs)
- Les types primitifs que le compilateur reconnaît, tels que des tableaux, des ensembles ou cordes Types
- qui mettent en œuvre IEnumerable
- Types qui mettent en œuvre la motif GetEnumerator tel que documenté dans le Guide du langage Delphi
Si vous regardez Generics.Collections.pas vous trouverez la mise en œuvre pour TDictionary<TKey,TValue>
où il a trois agents recenseurs pour TKey
, TValue
et types de TPair<TKey,TValue>
. Embarcadero montre qu'ils ont utilisé verbeux la mise en œuvre.
Vous pouvez faire quelque chose comme ceci:
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.
Cela vous permet de déclarer vos méthodes actuelles et MoveNext anonyme.
Autres conseils
Voir Dehl ( http://code.google.com/p/delphilhlplib/). Vous pouvez écrire un code qui ressemble à ceci:
for E in List.Where(...).Distinct.Reversed.Take(10).Select(...)... etc.
Tout comme vous pouvez le faire dans .NET (pas de syntaxe LINQ bien sûr).
Vous pouvez en finir avec l'usine et l'interface si vous ajoutez une fonction GetEnumerator()
à votre recenseur, comme ceci:
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;
et juste retour auto:
function TFilteredEnum.GetEnumerator: TSimpleEnumerator;
begin
result := Self;
end;
et Delphi va nettoyer facilement votre exemple pour vous, comme il le fait tout autre recenseur:
var
L: TMyList;
E: TMyObject;
begin
for E in TFilteredEnum.Create(L, 7) do ;
end;
Vous pouvez alors étendre votre recenseur à utiliser une méthode anonyme, que vous pouvez passer dans le constructeur:
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;
appeler comme ceci:
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;
Ensuite vous pouvez même en faire un générique, mais je wont faire ici, ma réponse est assez long car il est.
N @
J'utilise cette approche ... où les aproc effectue le test du filtre.
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;