Лучший способ реализовать фильтрованный перечислитель по TList <TMyObject>
Используя 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 с параметром напрямую?
Позднее Edit:
Я решил использовать идею Роберта Лава о внедрении счетчика, используя анонимные методы, и использовать gabr "record" factory, чтобы сохранить еще один класс. Это позволяет мне создать новый перечислитель с кодом, используя только несколько строк кода в функции, не требуется объявление нового класса.
Здесь, как объявлен мой общий счетчик в библиотечном блоке:
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;
И вот способ его использования. В любом классе я могу добавить такую функцию (и я намеренно создаю счетчик, который не использует List<T>
, чтобы показать силу этой концепции):
type Form1 = class(TForm)
protected
function Numbers(From, To:Integer):TGenericAnonEnumFactory<Integer>;
end;
// This is all that 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;
Ответы
Ответ 1
Delphi Для поддержки цикла в циклах требуется следующее: (Из Документов)
- Примитивные типы, которые компилятор
распознает, например, массивы, наборы или
строки
- Типы, которые реализуют
IEnumerable
- Типы, реализующие
Шаблон GetEnumerator как задокументированный
в Руководстве по языку Delphi.
Если вы посмотрите на Generics.Collections.pas, вы найдете реализацию для TDictionary<TKey,TValue>
, где у нее есть три перечисления для типов TKey
, TValue
и TPair<TKey,TValue>
. 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.
Ответ 2
См. DeHL (http://code.google.com/p/delphilhlplib/). Вы можете написать код, который выглядит так:
for E in List.Where(...).Distinct.Reversed.Take(10).Select(...)... etc.
Точно так же, как вы можете сделать в .NET(без синтаксиса linq, конечно).
Ответ 3
Вы подходите хорошо. Я не знаю лучшего способа.
Перечислитель factory также может быть реализован как запись, а не интерфейс.
Возможно, вы получите некоторые идеи здесь.
Ответ 4
Вы можете покончить с factory и интерфейсом, если вы добавили функцию 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;
назовите его следующим образом:
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 @
Ответ 5
Я использую этот подход... где 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;