Как правильно сделать поддержку интерфейса итерацией? - PullRequest
5 голосов
/ 17 июня 2011

Как я могу представить этот TList из интерфейса, как IEnumerator или IEnumerator<IFungibleTroll>? Я использую Delphi XE.

Вот как далеко я добрался:

unit FungibleTrollUnit;

interface

uses
  Windows, Messages, SysUtils,
  Variants, Classes, Graphics,
  Controls, Forms,
  Generics.Collections;

type
  IFungibleTroll = interface
    ['{03536137-E3F7-4F9B-B1F5-2C8010A4D019}']
       function GetTrollName:String;
       function GetTrollRetailPrice:Double;
  end;


  TFungibleTrolls = class (TInterfacedObject,IEnumerable<IFungibleTroll>)
      protected
         FTrolls:TList<IFungibleTroll>;

      public
          // IEnumerable
          function GetEnumerator:IEnumerator<IFungibleTroll>;//
//          function GetEnumerator:IEnumerator; overload;

         // find/search app feature requires searching.
         // this

         function FindSingleItemByName(aName:String;patternMatch:Boolean):IFungibleTroll;
         function FindMultipleItemsByName(aName:String;patternMatch:Boolean):IEnumerable<IFungibleTroll>;
         function FindSingleItemByIdentifier(anIdentifer:String):IFungibleTroll;// use internal non-visible identifier to find an app.

         constructor Create;

         property Trolls:TList<IFungibleTroll> read FTrolls; // implements IEnumerable<IFungibleTroll>;??
  private
  end;




implementation


{ TFungibleTrolls }

constructor TFungibleTrolls.Create;
begin
         FTrolls := TList<IFungibleTroll>.Create;

end;

function TFungibleTrolls.FindMultipleItemsByName(aName: String;
  patternMatch: Boolean): IEnumerable<IFungibleTroll>;
begin

end;

function TFungibleTrolls.FindSingleItemByIdentifier(
  anIdentifer: String): IFungibleTroll;
begin

end;

function TFungibleTrolls.FindSingleItemByName(aName: String;
  patternMatch: Boolean): IFungibleTroll;
begin

end;



function TFungibleTrolls.GetEnumerator: IEnumerator<IFungibleTroll>;
begin
  result := FTrolls.GetEnumerator;
end;

//function TFungibleTrolls.GetEnumerator: IEnumerator;
//begin
//  result := FTrolls.GetEnumerator; // type IEnumerator<IFungibleTroll> or IEnumerator?
//end;


end.

Я застрял в одной из трех ошибок, которые не могу понять, как решить:

[DCC Error] FungibleTrollUnit.pas(26): E2252 Method 'GetEnumerator' with identical parameters already exists -или-

[DCC Error] FungibleTrollUnit.pas(19): E2291 Missing implementation of interface method IEnumerable.GetEnumerator -или же- [DCC Error] FungibleTrollUnit.pas(19): E2291 Missing implementation of interface method IEnumerable.GetEnumerator

Кажется, я должен объявить две формы GetEnumerator, если я объявляю TFungibleTrolls для реализации IEnumerable, но я не могу понять, как это сделать, либо с перегрузками, либо без перегрузок, либо с помощью предложения разрешения метода ", вот так:

      function IEnumerable.GetEnumerator = GetPlainEnumerator; // method resolution clause needed?
      function GetEnumerator:IEnumerator<IFungibleTroll>;
      function GetPlainEnumerator:IEnumerator;

Возможно, это довольно простое использование IEnumerable и создание интерфейса поддержки итерации, и все же я застрял.

Обновление: кажется, когда я пытаюсь сделать это без предварительного объявления List<T>, я падаю в трещину, вызванную тем фактом, что IEnumerable<T> наследуется от IEnumerable, и все же, вместо Метод get get enumerator, мой класс должен предоставлять несколько классов, и поскольку мой класс не является универсальным, он не может "сопоставить себя" с требованиями IEnumerable напрямую, если я не использую обобщенное объявление List<T>. Образец Марьян работает, когда он скомпилирован в проект (.dproj + .dpr), но не встроен в пакет (.dproj + .dpk) и скомпилирован в IDE. Он отлично работает из командной строки, в пакете, но не в IDE, в пакете.

Ответы [ 2 ]

5 голосов
/ 17 июня 2011

Не ответ на ваш вопрос напрямую (все еще работаю над этим), но это то, что я сделал, чтобы получить «интерфейсный перечислитель», то есть и интерфейсный класс, который поддерживает итерацию:

IList<T> = interface(IInterface)
  [...]
  function GetEnumerator: TList<T>.TEnumerator;
  function Add(const Value: T): Integer;
end;

type
  TBjmInterfacedList<T> = class(TBjmInterfacedObject, IList<T>)
  strict private
    FList: TList<T>;
    function GetEnumerator: TList<T>.TEnumerator;
  strict protected
    function Add(const Value: T): Integer;
  public
    constructor Create; override;
    destructor Destroy; override;
  end;

implementation

constructor TBjmInterfacedList<T>.Create;
begin
  inherited;
  FList := TList<T>.Create;
end;

destructor TBjmInterfacedList<T>.Destroy;
begin
  FreeAndNil(FList);
  inherited;
end;

function TBjmInterfacedList<T>.GetEnumerator: TList<T>.TEnumerator;
begin
  Result := FList.GetEnumerator;
end;

function TBjmInterfacedList<T>.Add(const Value: T): Integer;
begin
  Result := FList.Add(Value);
end;

И затемВы можете делать что-то вроде:

ISite = interface(IInterface)
  ...
end;
ISites = interface(IList<ISite>);
  ...
end;

var
  for Site in Sites do begin
    ...
  end;

с такими классами реализации:

TSite = class(TBjmInterfacedObject, ISite)
  ...
end;
TSites = class(TBjmInterfacedList<ISite>, ISites)
  ...
end;

Обновление

Пример исходного кода проекта, загруженного в http://www.bjmsoftware.com/delphistuff/stackoverflow/interfacedlist.zip

3 голосов
/ 25 февраля 2013

Если вы действительно хотите создать класс, который реализует IEnumerable , вы можете сделать это так:

unit uGenericEnumerable;

interface

uses SysUtils, Classes, Generics.Collections;

type TGenericEnumerator<T> = class(TInterfacedObject, IEnumerator, IEnumerator<T>)
  private
    FList: TList<T>;
    FIndex: Integer;
  protected
    function GenericGetCurrent: T;
  public
    constructor Create(AList: TList<T>);
    procedure Reset;
    function MoveNext: Boolean;
    function GetCurrent: TObject;
    function IEnumerator<T>.GetCurrent = GenericGetCurrent;

    property Current: T read GenericGetCurrent;
end;

type TNonGenericEnumerable = class(TInterfacedObject, IEnumerable)
  protected
    function GetNonGenericEnumerator: IEnumerator; virtual; abstract;
  public
    function IEnumerable.GetEnumerator = GetNonGenericEnumerator;
end;

type TGenericEnumerable<T> = class(TNonGenericEnumerable, IEnumerable<T>)
  private
    FList: TList<T>;
  public
    constructor Create;
    destructor Destroy; override;
    function GetNonGenericEnumerator: IEnumerator; override;
    function GetEnumerator: IEnumerator<T>;
    property List: TList<T> read FList;
end;

implementation

{ TGenericEnumerator<T> }

constructor TGenericEnumerator<T>.Create(AList: TList<T>);
begin
  inherited Create;
  FList := AList;
  FIndex := -1;
end;

procedure TGenericEnumerator<T>.Reset;
begin
  FIndex := -1;
end;

function TGenericEnumerator<T>.MoveNext: Boolean;
begin
  if FIndex < FList.Count then
  begin
    Inc(FIndex);
    Result := FIndex < FList.Count;
  end
  else
  begin
    Result := False;
  end;
end;

function TGenericEnumerator<T>.GenericGetCurrent: T;
begin
  Result := FList[FIndex];
end;

function TGenericEnumerator<T>.GetCurrent: TObject;
begin
  // If T has not been constrained to being a class, raise an exception instead of trying to return an object.
  raise Exception.Create('Cannot use this as a non-generic enumerator');
  // If T has been constrained to being a class, return GenericGetCurrent.
  // Result := GenericGetCurrent;
end;


{ TGenericEnumerable<T> }

constructor TGenericEnumerable<T>.Create;
begin
  inherited Create;
  FList := TList<T>.Create;
end;

destructor TGenericEnumerable<T>.Destroy;
begin
  FList.Free;
end;

function TGenericEnumerable<T>.GetEnumerator: IEnumerator<T>;
begin
  Result := TGenericEnumerator<T>.Create(FList);
end;

function TGenericEnumerable<T>.GetNonGenericEnumerator: IEnumerator;
begin
  Result := GetEnumerator;
end;

end.

Теперь ваш FungibleTrollUnit будет выглядеть примерно так:

unit FungibleTrollUnit;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics,
  Controls, Forms, Generics.Collections,
  uGenericEnumerable;

type
  IFungibleTroll = interface
    ['{03536137-E3F7-4F9B-B1F5-2C8010A4D019}']
       function GetTrollName:String;
       function GetTrollRetailPrice:Double;
  end;

  IFungibleTrolls = interface(IEnumerable<IFungibleTroll>)
    ['{090B45FB-2925-4BFC-AE97-5D3F54E1C575}']
      function GetTrolls: TList<IFungibleTroll>;
      function FindSingleItemByName(aName:String):IFungibleTroll;
      function FindMultipleItemsByName(aName:String):IEnumerable<IFungibleTroll>;

      property Trolls:TList<IFungibleTroll> read GetTrolls;
  end;


  TFungibleTrolls = class (TGenericEnumerable<IFungibleTroll>, IFungibleTrolls, IEnumerable<IFungibleTroll>)
      public
         function GetTrolls: TList<IFungibleTroll>;

         function FindSingleItemByName(aName: String): IFungibleTroll;
         function FindMultipleItemsByName(aName: String): IEnumerable<IFungibleTroll>;

         property Trolls:TList<IFungibleTroll> read GetTrolls;
  private
  end;

implementation

uses StrUtils;

{ TFungibleTrolls }

function TFungibleTrolls.GetTrolls: TList<IFungibleTroll>;
begin
  Result := List;
end;

function TFungibleTrolls.FindMultipleItemsByName(aName: String): IEnumerable<IFungibleTroll>;
  var FilteredTrolls: TGenericEnumerable<IFungibleTroll>;
  var Troll: IFungibleTroll;
begin
  FilteredTrolls := TGenericEnumerable<IFungibleTroll>.Create;
  for Troll in List do
  begin
    if Troll.GetTrollName = aName then
      FilteredTrolls.List.Add(Troll);
  end;
  Result := IEnumerable<IFungibleTroll>(FilteredTrolls);
end;

function TFungibleTrolls.FindSingleItemByName(aName: String): IFungibleTroll;
  var Troll: IFungibleTroll;
begin
  Result := nil;
  for Troll in List do
  begin
    if Troll.GetTrollName = aName then
      Result := Troll;
      break;
  end;
end;

end.

Обратите внимание, что реализация IEnumerable не работает, но IEnumerable работает.
Это происходит потому, что, если T не ограничен, вы не можете преобразовать T в объект TObject.
Если T является строкойили целое число, например, тогда IEnumerator не имеет TObject для возврата.

Если T ограничено быть классом, вы можете заставить работать реализацию IEnumerable.
Если вы ограничите T быть IInterface, вы можете получить IEnumerable для работы (Delphi 2010 и после (приведениеGenericGetCurrent для IInterface, затем для TObject)), но я сомневаюсь, что это преимущество.

Я бы предпочел использовать его без ограничений и обходиться без возможности повторять все как объекты TObject.

TGenericEnumerable .GetEnumerator не может использовать FList.GetEnumerator, поскольку TList .GetEnumerator не возвращает IEnumerator

Даже если вы можете реализовать TGenericEnumerable без определения TNonGenericEnumerable, например:

type TGenericEnumerable<T> = class(TInterfacedObject, IEnumerable, IEnumerable<T>)
  private
    FList: TList<T>;
  protected
    function GenericGetEnumerator: IEnumerator<T>;
  public
    constructor Create;
    destructor Destroy; override;
    function GetEnumerator: IEnumerator;
    function IEnumerable<T>.GetEnumerator = GenericGetEnumerator;

    property List: TList<T> read FList;
end;

недостатком этого является то, что если вы попытаетесь выполнить итерацию с использованием объекта TGenericEnumerable , а не интерфейса, GetEnumerator будет не-общий и вы можете только перебрать объекты TObject.

Обычные предостережения о смешении ссылок на интерфейс и его базовый объект.Если вы ссылаетесь на объект как на тип объекта, так и на IEnumerable <....>, то когда счетчик ссылок на интерфейс возвращается к нулю, объект будет освобожден, даже если у вас все еще есть ссылка на него как на объект,(Вот почему я определил IFungibleTrolls; чтобы я мог ссылаться на коллекцию как на интерфейс).

Вы можете сделать альтернативные реализации TGenericEnumerator ;например, он может содержать ссылку на список, индекс и предикат выбора, которые все предоставлены в конструкторе.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...