【问题标题】:How to properly make an interface support iteration?如何正确地使接口支持迭代?
【发布时间】:2011-09-17 09:02:09
【问题描述】:

如何从接口公开此 TList,如 IEnumeratorIEnumerator<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

如果我声明 TFungibleTrolls 来实现 IEnumerable,我似乎必须声明两种形式的 GetEnumerator,但我似乎无法弄清楚如何做到这一点,或者使用重载,或者没有重载,或者使用“方法解析子句” ",像这样:

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

这似乎是 IEnumerable 的一个非常基本的用法,并进行了接口支持迭代,但是,我被卡住了。

更新:似乎当我尝试在没有首先声明 List&lt;T&gt; 的情况下执行此操作时,我陷入了由于 IEnumerable&lt;T&gt; 继承自 IEnumerable 的事实而导致的裂缝,然而,而不是单个 get 枚举器方法,我的类必须提供多个,因为我的类不是泛型的,所以它不能直接“映射”到 IEnumerable 的要求,除非我使用泛型 List&lt;T&gt; 声明。 Marjan 的示例在编译到项目 (.dproj+.dpr) 时有效,但在构建到包 (.dproj+.dpk) 并在 IDE 中编译时无效。它在命令行中,在一个包中工作正常,但在 IDE 中,在一个包中。

【问题讨论】:

    标签: delphi list generics interface


    【解决方案1】:

    不是直接回答您的问题(仍在努力),但这是我为获得“接口枚举器”所做的,即支持迭代的接口类:

    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

    【讨论】:

    • 所以我避免使用 泛型声明而只是尝试“直奔目标”对我来说是一种风格上的错误吗?
    • 可能。否则您遇到的确实是需要两个 GetEnumerator 函数,而无需重载它们,因为它们没有可区分的参数...我试图为...寻找帮助以提供支持,但被 D2010 挫败帮助。我确信的一件事是,要获得……支持,您不需要声明对 IEnumerable 甚至 IEnumerator 的支持。您只需要为编译器提供必要的方法...
    • 当我尝试构建它时,我添加了我希望添加的内容,我得到 [DCC 致命错误] unit1.pas(1): F2084 Internal Error: AV221EBFB0-R00000014-0。
    • 哎呀。这听起来很糟糕。我将为您创建一个简单的控制台项目。等一下。
    • 因此编译器在不求助于泛型的情况下,缺乏让您声明所需方法的方法。有趣的。真的应该在某个地方的文档中!我有理由克服我对泛型的厌恶,因为如果我们要使用接口列表并将它们公开为可迭代对象,它们现在是必需的。直到我尝试在没有任何 东西的情况下做到这一点之前,我并不能 100% 确定你做不到。
    【解决方案2】:

    如果你真的想做一个实现 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

    即使您可以在不定义 TNonGenericEnumerable 的情况下实现 TGenericEnumerable,如下所示:

    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 的替代实现;例如,它可以包含对列表、索引和选择谓词的引用,这些都在构造函数中提供。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-05-13
      • 2016-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-02
      • 2015-09-05
      相关资源
      最近更新 更多