Comment créer un énumérateur personnalisé pour une classe dérivée de tdictionary?
-
15-11-2019 - |
Question
J'ai défini une collection dérivée de tdictionary et j'ai besoin de définir un énumérateur personnalisé qui applique un filtre supplémentaire.
Je suis coincé car je ne peux pas accéder au tableau Tdictionary Finetems (c'est privé), donc je ne peux pas définir la méthode MoveNext
Comment procéderiez-vous à redéfinir un énumérateur filtré sur une classe dérivée de la tdictionnaire?
Voici un code simple pour illustrer ce que je veux faire:
TMyItem = class(TObject)
public
IsHidden:Boolean; // The enumerator should not return hidden items
end;
TMyCollection<T:TMyItem> = class(TDictionary<integer,T>)
public
function GetEnumerator:TMyEnumerator<T>; // A value filtered enumerator
type
TMyEnumerator = class(TEnumerator<T>)
private
FDictionary: TMyCollection<integer,T>;
FIndex: Integer;
function GetCurrent: T;
protected
function DoGetCurrent: T; override;
function DoMoveNext: Boolean; override;
public
constructor Create(ADictionary: TMyCollection<integer,T>);
property Current: T read GetCurrent;
function MoveNext: Boolean;
end;
end;
function TMyCollection<T>.TMyEnumerator.MoveNext: Boolean;
begin
// In below code, FIndex is not accessible, so I can't move forward until my filter applies
while FIndex < Length(FDictionary.FItems) - 1 do
begin
Inc(FIndex);
if (FDictionary.FItems[FIndex].HashCode <> 0)
and not(FDictionary.FItems[FIndex].IsHidden) then // my filter
Exit(True);
end;
Result := False;
end;
La solution
Vous pouvez baser votre énumérateur sur TDictionary
L'énumerateur est donc vous n'avez pas vraiment besoin d'accéder à FItems
. Cela fonctionne même si vous écrivez une classe d'emballage autour TDictionary
Comme le suggère Barry. L'énumérateur ressemblerait à ceci:
TMyEnumerator = class
protected
BaseEnumerator: TEnumerator<TPair<Integer, T>>; // using the key and value you used in your sample
public
function MoveNext:Boolean;
property Current:T read GetCurrent;
end;
function TMyEnumerator.MoveNext:Boolean;
begin
Result := BaseEnumerator.MoveNext;
while Result and (not (YourTestHere)) do // ie: the base enumerator returns everything, reject stuff you don't like
Result := BaseEnumerator.MoveNext;
end;
function TMyEnumerator.Current: T;
begin
Result := BaseEnumerator.Current.Value; // Based on your example, it's value you want to extract
end;
Et voici une application complète de console 100 lignes qui le démontre:
program Project23;
{$APPTYPE CONSOLE}
uses
SysUtils, Generics.Collections;
type
TMyType = class
public
Int: Integer;
constructor Create(anInteger:Integer);
end;
TMyCollection<T:TMyType> = class(TDictionary<integer,T>)
strict private
type
TMyEnumerator = class
protected
BaseEnum: TEnumerator<TPair<Integer,T>>;
function GetCurrent: T;
public
constructor Create(aBaseEnum: TEnumerator<TPair<Integer,T>>);
destructor Destroy;override;
function MoveNext:Boolean;
property Current:T read GetCurrent;
end;
public
function GetEnumerator: TMyEnumerator;
end;
{ TMyCollection<T> }
function TMyCollection<T>.GetEnumerator: TMyEnumerator;
begin
Result := TMyEnumerator.Create(inherited GetEnumerator);
end;
{ TMyType }
constructor TMyType.Create(anInteger: Integer);
begin
Int := anInteger;
end;
{ TMyCollection<T>.TMyEnumerator }
constructor TMyCollection<T>.TMyEnumerator.Create(aBaseEnum: TEnumerator<TPair<Integer, T>>);
begin
BaseEnum := aBaseEnum;
end;
function TMyCollection<T>.TMyEnumerator.GetCurrent: T;
begin
Result := BaseEnum.Current.Value;
end;
destructor TMyCollection<T>.TMyEnumerator.Destroy;
begin
BaseEnum.Free;
inherited;
end;
function TMyCollection<T>.TMyEnumerator.MoveNext:Boolean;
begin
Result := BaseEnum.MoveNext;
while Result and ((BaseEnum.Current.Value.Int mod 2) = 1) do
Result := BaseEnum.MoveNext;
end;
var TMC: TMyCollection<TMyTYpe>;
V: TMyType;
begin
try
TMC := TMyCollection<TMyType>.Create;
try
// Fill TMC with some values
TMC.Add(1, TMyType.Create(1));
TMC.Add(2, TMyType.Create(2));
TMC.Add(3, TMyType.Create(3));
TMC.Add(4, TMyType.Create(4));
TMC.Add(5, TMyType.Create(5));
TMC.Add(6, TMyType.Create(6));
TMC.Add(7, TMyType.Create(7));
TMC.Add(8, TMyType.Create(8));
// Filtered-enum
for V in TMC do
WriteLn(V.Int);
ReadLn;
finally TMC.Free;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Autres conseils
Vous devriez écrire une classe qui s'enroule TDictionary
plutôt que d'hériter directement. La seule raison TDictionary
peut être hérité du tout pour que TObjectDictionary
pourrait être défini et rester polymorphe avec. C'est-à-dire le seul support approprié par le remplacement TDictionary
consiste à personnaliser ce qui se passe lorsque les clés et les valeurs sont supprimées du dictionnaire (donc elles peuvent avoir besoin d'être libérées).