2010-07-05 11 views
10

con Delphi 2010, diciamo che ho una classe dichiarata come questo:modo migliore per implementare enumeratore filtrata su TList <TMyObject>

TMyList = TList<TMyObject> 

Per questo elenco Delphi gentilmente ci fornisce un enumeratore, in modo che possiamo scrivere questo:

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

Il guaio è, vorrei scrivere questo:

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

Cioè , Voglio la possibilità di fornire più enumeratori per la stessa lista, utilizzando alcuni criteri. Sfortunatamente l'implementazione di for X in Z richiede la presenza di una funzione Z.GetEnumerator, senza parametri, che restituisce l'enumeratore specificato! Per aggirare questo problema, sto definendo un'interfaccia che implementa la funzione "GetEnumerator", quindi implemento una classe che implementa l'interfaccia e infine scrivo una funzione su TMyList che restituisce l'interfaccia! E sto restituendo un'interfaccia perché non voglio essere disturbato a liberare manualmente la classe molto semplice ... In ogni caso, questo richiede MOLTA tipizzazione. Ecco come questo sarebbe simile:

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; 

Usando questo posso finalmente scrivere:

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

Conoscete un modo migliore di fare questo? Forse Delphi supporta un modo per chiamare direttamente GetEnumerator con un parametro?

Edit Più tardi:

ho deciso di utilizzare l'idea di Robert Love di attuare l'enumeratore utilizzando i metodi anonimi e l'utilizzo di fabbrica "record" di Gabr per salvare ancora un altra classe. Ciò mi consente di creare un enumeratore nuovo di zecca, completo di codice, utilizzando solo poche righe di codice in una funzione, senza richiedere una nuova dichiarazione di classe.

Ecco come il mio enumeratore generico viene dichiarato, in un'unità libreria:

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; 

Ed ecco un modo per usarlo. Su qualsiasi classe che posso aggiungere una funzione come questa (e sto volutamente creare un enumeratore che non utilizza un List<T> per mostrare la potenza di questo concetto):

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; 

Ed ecco come userei questo nuovo enumeratore:

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

Grazie per questo, ora l'implementazione del supporto dell'enumeratore per le classi è diventato molto più semplice. Se si desidera utilizzare qualcosa come 'per obj in list do', basta dichiarare GetEnumerator() in questo modo: 'function GetEnumerator: TEnumGenericAnonim ' e nell'implementazione è sufficiente aggiungere '.GetEnumerator' alla fine dell'istruzione Create. – Sharken

risposta

4

Delphi Per in loop supporto richiede una delle seguenti operazioni: (From the Docs)

  • tipi primitivi che il compilatore riconosce, ad esempio matrici, insiemi o stringhe
  • Tipi che implementano IEnumerable
  • Tipi che implementano il pattern GetEnumerator come documentato nella Delphi Language Guide

Se si guarda al Generics.Collections.pas troverete l'implementazione per TDictionary<TKey,TValue> dove dispone di tre enumeratori per TKey, TValue e TPair<TKey,TValue> tipi. Embarcadero mostra che hanno usato un'implementazione dettagliata.

Si potrebbe fare qualcosa di simile:

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. 

Ciò consentirebbe di dichiarare i vostri metodi correnti e MoveNext anonimo.

+1

L'idea di utilizzare metodi anonimi per implementare l'enumeratore è assolutamente geniale, mi impedisce di creare innumerevoli classi per implementare diversi enumeratori. Dopo tutto il codice dell'enumeratore è in genere piuttosto semplice e può essere implementato in poche righe di codice. Grazie. –

1

Io uso questo approccio ... dove l'AProc esegue il test del filtro.

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; 
+0

Grazie Nige, ma questo non risponde alla mia domanda. Ho chiesto modi per implementare l'enumeratore perché voglio usare il ciclo "for E in L". –

+0

Questa è una buona idea (anche se non del tutto pertinente alla domanda) e in realtà non merita i downvotes. – gabr

8

Vedere DeHL (http://code.google.com/p/delphilhlplib/). È possibile scrivere codice che assomiglia a questo:

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

Proprio come si può fare in .NET (non la sintassi LINQ naturalmente).

+0

Grazie per il link. Anche se alla fine dovessi usare DeHL, preferirei la risposta di Robert Love perché mostra una caratteristica linguistica che mi consente di implementarla da sola. –

+0

Sicuro. DeHL è lì per farti risparmiare tempo se non vuoi investire in questo tipo di cose. Se preferisci scrivere la tua implementazione leggera, la risposta di Robert è quella giusta. – alex

6

L'approccio è soddisfacente. Non conosco un modo migliore.

La fabbrica di numeratori può anche essere implementata come record anziché come interfaccia.

Forse avrai qualche idea here.

+0

L'utilizzo di un record invece di un'interfaccia mi salva ancora un'altra "classe". Grazie. –

3

È possibile farla finita con la fabbrica e l'interfaccia se aggiungere una funzione GetEnumerator() al vostro enumeratore, in questo modo:

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; 

e proprio ritorno di sé:

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

e Delphi comodamente volontà ripulisci la tua istanza per te, proprio come fa qualsiasi altro enumeratore:

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

È quindi possibile estendere la vostra enumeratore per usare un metodo anonimo, che è possibile passare nel costruttore:

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; 

chiamata in questo modo:

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; 

Poi si potrebbe anche fare un generico , ma non lo farò qui, la mia risposta è abbastanza lunga così com'è.

N @

+0

Ottime idee! (imbottitura) – gabr