如何实现IEnumerable <T>?

Ian*_*oyd 16 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;
Run Code Online (Sandbox Code Playgroud)

我会有一个实现:

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;
Run Code Online (Sandbox Code Playgroud)

现在,为了成为一名优秀的程序员,我会选择我的班级也支持这个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;
Run Code Online (Sandbox Code Playgroud)

那些知道这是怎么回事的人会知道实施IEnumerable是一个红鲱鱼; 并且必须以任何一种方式完成.

现在出现了问题

该代码无法编译,因为:

function GetEnumerator: IEnumerator<T>;
function GetEnumerator: IEnumerator;
Run Code Online (Sandbox Code Playgroud)

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

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

好的,我会把它们标记为overloads:

function GetEnumerator: IEnumerator<T>; overload;
function GetEnumerator: IEnumerator; overload;
Run Code Online (Sandbox Code Playgroud)

但这不起作用:

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

接口方法解析

重载是错误的方法,我们应该寻找方法解决条款:

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;
Run Code Online (Sandbox Code Playgroud)

由于这不能编译,出于逃避我的原因:

E2291缺少接口方法IEnumerable.GetEnumerator的实现

让我们忘记吧 IEnumerable

假装我不在乎我的班级不支持IEnumerable,让我们把它作为支持的界面删除.它实际上并没有改变任何东西,因为它IEnumerable<T>来自IEnumerble:

IEnumerable<T> = interface(IEnumerable)
  function GetEnumerator: IEnumerator<T>;
end;
Run Code Online (Sandbox Code Playgroud)

所以该方法必须存在,并删除代码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;
Run Code Online (Sandbox Code Playgroud)

由于同样的原因不编译:

E2291缺少接口方法IEnumerable.GetEnumerator的实现

好吧那么,忘记泛型

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

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

{ TStackoverflow }

function TStackoverflow.GetEnumerator: IEnumerator;
begin

end;
Run Code Online (Sandbox Code Playgroud)

非常好,有效.我可以得到我的班级支持IEnumerable.现在我想支持IEnumerable<T>.

这引出了我的问题:

如何实现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.
Run Code Online (Sandbox Code Playgroud)

Dav*_*nan 10

一个方法解决条款,可以做的工作:

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;
Run Code Online (Sandbox Code Playgroud)

您的尝试似乎失败了,因为您的方法都没有被命名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;
Run Code Online (Sandbox Code Playgroud)

现在,请注意,使用接口继承意味着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;
Run Code Online (Sandbox Code Playgroud)

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

遗憾的是,您不能这样做,IEnumerable<T>因为继承的方法与引入的新方法具有相同的名称IEnumerable<T>.

再次感谢Stefan带领我进行启蒙.

  • 要点是,“IEnumerable.GetEnumerator”的方法解析子句只会为“IEnumerable”本身处理它,而不是在从“IEnumerable&lt;T&gt;”访问它时处理它。但由于这些方法具有相同的名称,因此您无法引用通过“IEnumerable&lt;T&gt;.GetEnumerator”继承的非泛型方法。方法解析子句不会将此解析应用于从我有一些方法解析子句的接口类型继承的任何其他接口。这意味着实际上如果您要实现“IEnumerable”和“IEnumerable&lt;T&gt;”,您必须实现 3 个 GetEnumerator 方法。 (2认同)

uml*_*cat 9

快速简答

两个名称相同的接口组,例如:非泛型IEnumerable和:IEnumerable<T>.

概观

有同样的问题.

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

通用接口有一种特殊情况IEnumerable<T>,因为它们也有非通用IEnumerable接口.此接口用于与OS兼容,它们不仅仅是另一个编程接口.

并且,还有一些其他通用和非通用父接口,IEnumerator或者IComparable具有自己的成员定义.

为了解决同样的问题,我寻找哪些父接口,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;
Run Code Online (Sandbox Code Playgroud)

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

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

因此,我应用了中间类,因为它允许我找出哪些接口和各个成员丢失的地方.

干杯.