Лучший способ реализовать фильтрованный перечислитель на 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 с параметром непосредственно?

Более позднее редактирование:

Я решил использовать идею 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;
10
задан Cosmin Prund 6 July 2010 в 06:31
поделиться

5 ответов

Для поддержки цикла Delphi For требуется следующее: ( Из Документов )

  • Типы примитивов, которые компилятор распознает, например, массивы, множества или strings
  • Типы, реализующие IEnumerable
  • Типы, реализующие Шаблон GetEnumerator, как описано в документации в Руководстве по языку Delphi

Если вы посмотрите 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.

4
ответ дан 3 December 2019 в 17:57
поделиться

См. DeHL ( http://code.google.com/p/delphilhlplib/ ). Вы можете написать такой код:

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

То же, что и в .NET (конечно, без синтаксиса linq).

8
ответ дан 3 December 2019 в 17:57
поделиться

Вы подходите нормально. Я не знаю лучшего способа.

Фабрика перечислителей также может быть реализована как запись вместо интерфейса.

Возможно, вы почерпнете какие-нибудь идеи здесь .

6
ответ дан 3 December 2019 в 17:57
поделиться

Вы можете покончить с фабрикой и интерфейсом, если добавите в свой перечислитель функцию 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 @

3
ответ дан 3 December 2019 в 17:57
поделиться

Я использую этот подход ... когда 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;
1
ответ дан 3 December 2019 в 17:57
поделиться
Другие вопросы по тегам:

Похожие вопросы: