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;
Était-ce utile?

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 approche est très bien. Je ne sais pas de quelque façon que mieux.

L'usine recenseur peut également être mis en œuvre comme un enregistrement au lieu d'une interface.

Peut-être que vous aurez quelques idées .

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;
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top