如何实现IEnumerable <t>?</t>

时间:2014-10-24 14:29:17

标签: delphi generics delphi-xe6

如何实施IEnumerable<T>

背景

假设我有一个我想要实现的课程IEnumerable<T>

TStackoverflow<T> = class(TInterfacedObject, IEnumerable<T>)
public
   { IEnumerable<T> }
   function GetEnumerator: IEnumerator<T>;
end;

var
    IEnumerable<TMouse> mices = TStackoverflow<TMouse>.Create;

我会有一个实现:

TStackoverflow<T> = class(TInterfacedObject, IEnumerable<T>)
public
   { IEnumerable<T> }
   function GetEnumerator: IEnumerator<T>;
end;

function TStackoverflow<T>.GetEnumerator: IEnumerator<T>;
begin
    Result := {snip, not important here};
end;

现在,为了成为一名优秀的程序员,我会选择我的班级也支持IEnumerable界面:

TStackoverflow<T> = class(TInterfacedObject, IEnumerable<T>, IEnumerable)
public
   { IEnumerable<T> }
   function GetEnumerator: IEnumerator<T>;

   { IEnumerable }
   function GetEnumerator: IEnumerator;
end;

function TStackoverflow<T>.GetEnumerator: IEnumerator<T>;
begin
    Result := {snip, not important here};
end;

function TStackoverflow.GetEnumerator: IEnumerator;
begin
    Result := {snip, not important here};
end;

那些知道这种情况的人会知道实施IEnumerable是一个红鲱鱼;并且必须以任何一种方式完成。

现在出现了问题

该代码无法编译,因为:

function GetEnumerator: IEnumerator<T>;
function GetEnumerator: IEnumerator;

我有两个具有相同签名的方法(不是真正相同的签名,但同样足以让Delphi无法区分它们):

  

E2254重载过程'GetEnumerator'必须标有'overload'指令

好的,我会将它们标记为overloads

function GetEnumerator: IEnumerator<T>; overload;
function GetEnumerator: IEnumerator; overload;

但这不起作用:

  

E2252具有相同参数的方法'GetEnumerator'已存在

接口方法解析

重载是错误的方法,我们应该关注method resolution clauses

type
    TStackoverflow<T> = class(TInterfacedObject, IEnumerable<T>, IEnumerable)
    protected
        function GetEnumeratorTyped: IEnumerator<T>;
        function GetEnumeratorGeneric: IEnumerator;
    public
        function IEnumerable<T>.GetEnumerator = GetEnumeratorTyped;
        function IEnumerable.GetEnumerator = GetEnumeratorGeneric;
    end;

{ TStackoverflow }

function TStackoverflow<T>.GetEnumeratorGeneric: IEnumerator;
begin

end;

function TStackoverflow<T>.GetEnumeratorTyped: IEnumerator<T>;
begin

end;

除了这个也没有编译,出于逃避我的原因:

  

E2291缺少接口方法IEnumerable.GetEnumerator

的实现

所以让我们忘记IEnumerable

假装我不在乎我的班级是否不支持IEnumerable,请将其作为支持的界面删除。它实际上并没有改变任何内容,因为IEnumerable<T>来自IEnumerble

IEnumerable<T> = interface(IEnumerable)
  function GetEnumerator: IEnumerator<T>;
end;

所以方法必须,以及删除IEnumerable的代码:

type
    TStackoverflow<T> = class(TInterfacedObject, IEnumerable<T>)
    protected
        function GetEnumeratorTyped: IEnumerator<T>;
    public
        function IEnumerable<T>.GetEnumerator = GetEnumeratorTyped;
    end;

{ TStackoverflow }

function TStackoverflow<T>.GetEnumeratorTyped: IEnumerator<T>;
begin

end;
由于同样的原因,

无法编译:

  

E2291缺少接口方法IEnumerable.GetEnumerator

的实现

好吧,那就忘记了泛型

让我们停下来,合作并倾听。 IEnumerable又回到了一个古老的新发明:

type
    TStackoverflow = class(TInterfacedObject, IEnumerable)
    public
        function GetEnumerator: IEnumerator;
    end;

{ TStackoverflow }

function TStackoverflow.GetEnumerator: IEnumerator;
begin

end;

很好,这很有效。我可以得到我的班级支持IEnumerable。现在我想支持IEnumerable<T>

这引出了我的问题:

  

How to implement IEnumerable<T>?


更新:忘记附加完整的非功能代码:

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils;

type
    TStackoverflow<T> = class(TInterfacedObject, IEnumerable<T>, IEnumerable)
    protected
        function GetEnumeratorTyped: IEnumerator<T>;
        function GetEnumeratorGeneric: IEnumerator;
    public
        function IEnumerable<T>.GetEnumerator = GetEnumeratorTyped;
        function IEnumerable.GetEnumerator = GetEnumeratorGeneric;
    end;

{ TStackoverflow<T> }

function TStackoverflow<T>.GetEnumeratorGeneric: IEnumerator;
begin

end;

function TStackoverflow<T>.GetEnumeratorTyped: IEnumerator<T>;
begin

end;

begin
  try
     { TODO -oUser -cConsole Main : Insert code here }
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

3 个答案:

答案 0 :(得分:10)

method resolution clause可以胜任:

type
  TStackoverflow<T> = class(TInterfacedObject, IEnumerable<T>, IEnumerable)
  public
    { IEnumerable<T> }
    function GetEnumeratorGeneric: IEnumerator<T>;
    function IEnumerable<T>.GetEnumerator = GetEnumeratorGeneric;

    { IEnumerable }
    function GetEnumerator: IEnumerator;
    function IEnumerable.GetEnumerator = GetEnumerator;
  end;

您的尝试似乎失败了,因为您的方法都没有被命名为GetEnumerator。在我上面的代码中,其中一个名为GetEnumerator,另一个名称不同。从我的实验中,你必须有一个与接口方法同名的实现方法。

这很奇怪。我说这看起来像编译器错误。该行为甚至持续到XE7。

另一方面,至少你现在有办法向前迈进。

<强>更新

好的,Stefan在下面的评论引导我,姗姗来迟地了解到底发生了什么。实际上你需要在这里满足三种接口方法。显然,我们必须为IEnumerable.GetEnumerator提供实施,以满足IEnumerable。但由于IEnumerable<T>来自IEnumerable,因此IEnumerable<T>包含两种方法IEnumerable.GetEnumeratorIEnumerable<T>.GetEnumerator。所以你真正希望能做的是这样的事情:

我发现有点难以跟踪冲突的名字和泛型,所以我打算提供另一个例子,我认为更清楚地提出关键点:

type
  IBase = interface
    procedure Foo;
  end;

  IDerived = interface(IBase)
    procedure Bar;
  end;

  TImplementingClass = class(TInterfacedObject, IBase, IDerived)
    procedure Proc1;
    procedure IBase.Foo = Proc1;

    procedure Proc2;
    procedure IDerived.Bar = Proc2;
  end;

现在,请注意,使用接口继承意味着IDerived包含两个方法FooBar

上面的代码不会编译。编译器说:

  

E2291缺少接口方法IBase.Foo的实现

这当然看起来很奇怪,因为方法解决条款当然处理了这个问题。嗯,不,方法解决方案涉及Foo中声明的IBase,而不是IDerived中继承的TImplementingClass = class(TInterfacedObject, IBase, IDerived) procedure Proc1; procedure IBase.Foo = Proc1; procedure IDerived.Foo = Proc1; procedure Proc2; procedure IDerived.Bar = Proc2; end;

在上面的例子中,我们可以很容易地解决问题:

IEnumerable<T>

这足以让编译器满意。我们现在已经为所有需要实施的三种方法提供了实现。

很遗憾,您无法使用IEnumerable<T>执行此操作,因为继承的方法与{{1}}引入的新方法具有相同的名称。

再次感谢Stefan带领我进入启蒙。

答案 1 :(得分:9)

快速简答

两个接口组具有相同的名称,例如:非通用IEnumerable和:IEnumerable<T>

<强>概述

遇到同样的问题。

问题不在于接口实现或通用接口实现。

通用接口(如IEnumerable<T>)存在特殊情况,因为它们还具有非通用IEnumerable接口。这个接口是为了与O.S.兼容,它们不仅仅是另一个编程接口。

此外,还有一些其他通用和非通用父接口,如IEnumeratorIComparable,它们都有自己的成员定义。

<强>解决方案

为了解决同样的问题,我会注意哪些父接口IEnumerable<T>具有,并检测匹配的非通用接口。

并且,实现了非泛型接口的添加和中间非泛型类。这使我可以轻松地检测出哪些接口和接口。匹配成员定义缺失。

type
    // Important: This intermediate class declaration is NOT generic

    // Verify that non generic interface members implemented

    TStackoverflowBase = class(TInterfacedObject, IEnumerable)
    protected
        function GetEnumeratorGeneric: IEnumerator;
    public
        function IEnumerable.GetEnumerator = GetEnumeratorGeneric;
    end;

    // Important: This proposed class declaration IS generic,
    // yet, it descends from a non generic intermediate class.

    // After verify that non generic interface members implemented,
    // verify that generic interface members implemented,

    TStackoverflow<T> = class(TStackoverflowBase, IEnumerable<T>)
    protected
        function GetEnumeratorTyped: IEnumerator<T>;
    public
        function IEnumerable<T>.GetEnumerator = GetEnumeratorTyped;
    end;

事情,更难,因为有几个成员重载了相同的标识符,但不同的参数类型或不同的返回类型, 取决于它们是来自通用接口还是来自非通用接口。

@David Heffernan只有一个类的解决方案,并不坏,我确实尝试了相同的方法,但它更加复杂。

因此,我应用了中间类,因为它允许我找出哪个接口和&amp;各个成员,失踪。

干杯。

答案 2 :(得分:0)

实际上,它很简单,虽然不是很明显。关键是要知道编译器对枚举器有一些魔力。

首先,我们根本不需要实施IEnumeratorIEnumerator<T>。相反,我们声明或拥有接口。为了这个示例,我们列举了WideStrings,因此它可能如下所示。请注意,方法/属性名称和签名在此非常重要:

IFoobarEnumerator = interface
  function GetCurrent: WideString;
  function MoveNext: Boolean;
  procedure Reset;
  property Current: WideString read GetCurrent;
end;

现在,周围的类得到一个名为GetEnumerator的方法,它返回一个看起来像枚举器的东西。

我刻意写了&#34; 看起来像一个枚举者的东西&#34;而不是&#34;实现IEnumerator<T>&#34;的东西,因为那&#39 ;所有编译器需要让魔法发生:

function  GetEnumerator : IFoobarEnumerator;  

除了我们当然仍然需要为IFoobarEnumerator实现枚举器类这一事实之外 - 这就是它。我们已经完成了。