Используя 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 с параметром непосредственно?
Более позднее редактирование:
Я решил использовать идею Robert Love реализовать перечислитель с помощью анонимных методов и с помощью "рекордной" фабрики 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;
И вот способ использовать его. На любом классе я могу добавить функцию как это (и я намеренно создаю перечислитель, который не использует a 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 требуется следующее: ( Из Документов )
Если вы посмотрите Generics.Collections.pas, вы найдете реализацию для TDictionary
, где есть три перечислителя для TKey
, TValue
и TPair
типы. 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.
Это позволит вам анонимно объявить свои методы Current и MoveNext.
См. DeHL ( http://code.google.com/p/delphilhlplib/ ). Вы можете написать такой код:
for E in List.Where(...).Distinct.Reversed.Take(10).Select(...)... etc.
То же, что и в .NET (конечно, без синтаксиса linq).
Вы подходите нормально. Я не знаю лучшего способа.
Фабрика перечислителей также может быть реализована как запись вместо интерфейса.
Возможно, вы почерпнете какие-нибудь идеи здесь .
Вы можете покончить с фабрикой и интерфейсом, если добавите в свой перечислитель функцию 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;
и просто верните self:
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;
назовите его как this:
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;