How to implement IEnumerable<T>?
Asked Answered
P

3

19

How do i implement IEnumerable<T>?

Background

Lets say i have a class that i want to implement IEnumerable<T>:

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

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

i would have an implementation:

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;

Now, in order to be a good programmer, i will choose that my class will also support the IEnumerable interface:

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;

Those of you who know where this is going, will know that implementing IEnumerable is a red-herring; and had to be done either way.

Now comes the problem

That code doesn't compile, because:

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

i have two methods with the same signature (not really the same signature, but same enough that Delphi can't distinguish between them):

E2254 Overloaded procedure 'GetEnumerator' must be marked with the 'overload' directive

Ok, fine, i'll mark them as overloads:

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

But that doesn't work:

E2252 Method 'GetEnumerator' with identical parameters already exists

Interface method resolution

Overloading was the wrong approach, we should be looking to 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;

Except this doesn't compile either, for reasons that escape me:

E2291 Missing implementation of interface method IEnumerable.GetEnumerator

So lets forget IEnumerable

Pretend i don't care if my class doesn't support IEnumerable, lets remove it as a supported interface. It doesn't actually change anything, as IEnumerable<T> descends from IEnumerble:

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

so the method must exist, and the code that removes 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;

doesn't compile for the same reason:

E2291 Missing implementation of interface method IEnumerable.GetEnumerator

Alright then, forget generics

So lets stop, collaborate and listen. IEnumerable is back as an old new invention:

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

{ TStackoverflow }

function TStackoverflow.GetEnumerator: IEnumerator;
begin

end;

Excellent, that works. I can have my class support IEnumerable. Now i want to support IEnumerable<T>.

Which leads me to my question:

How to implement IEnumerable<T>?


Update: Forgot to attach complete non-functional code:

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.
Palladium answered 24/10, 2014 at 14:29 Comment(4)
The reason you cannot overload those two methods is that they do have identical parameter lists. Specifically no parameters at all. Overload resolution does not take into account return value type.Elveraelves
You may want to replace your TStackoverflow<T> identifier for a TStackoverflowCollection<T> identifier, since IEnumerable usually applied for collections & objects composed by others objects.Tedmann
That question alone deserves 5000 upvotes more. It still ceases to amaze me how Emba could turn a basically simple concept into such an horrible nightmare for the average programmer. One might compare the above question with the simplicity and elegancy of implementing IEnumerable<T> in C#. Especially with yield return now the latter is done literally in minutes. To ask the question alone is 10x that time, let alone the time spent before asking that question on SO. (Yes, I am aware that yield return might not be suitable in all cases and there was no yield return back then).Benadryl
@Benadryl I actually stopped using generics with interfaces altogether. There are occasional compiler bugs where you do Build All and the compiler can't find the class you're constructing. Do Build All again and it works. The Delphi compiler reeks of a barely functional, cobbled together, tangled, mess. I wish C# had the same powerful widget library that Delphi does.Palladium
E
14

A method resolution clause can do the job:

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;

Your attempt failed, it seems, because neither of your methods was named GetEnumerator. In my code above, one of them is called GetEnumerator and the other one is given a different name. From my experimentation, you have to have exactly one of the implementing methods having the same name as the interface method.

Which is weird. I'd say that this looks like a compiler bug. The behaviour persists even to XE7.

On the other hand, at least you have a way to move forward now.

Update

OK, Stefan's comment below leads me to, belatedly, understand what is really going on. You actually need to satisfy three interface methods here. Clearly we have to provide an implementation for IEnumerable.GetEnumerator, in order to satisfy IEnumerable. But since IEnumerable<T> derives from IEnumerable, then IEnumerable<T> contains two methods, IEnumerable.GetEnumerator and IEnumerable<T>.GetEnumerator. So what you really want to be able to do is something like this:

I'm finding it a little hard to keep track of the clashing names and the generics, so I'm going to offer an alternative example that I think draws out the key point more clearly:

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;

Now, note that the use of interface inheritance means that IDerived contains two methods, Foo and Bar.

The code above won't compile. The compiler says:

E2291 Missing implementation of interface method IBase.Foo

Which of course seems odd because surely the method resolution clause dealt with that. Well, no, the method resolution dealt with the Foo declared in IBase, but not the one in IDerived that was inherited.

In the example above we can solve the problem quite easily:

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

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

This is enough to make the compiler happy. We've now provided implementations for all three methods that need to be implemented.

Unfortunately you cannot do this with IEnumerable<T> because the inherited method has the same name as the new method introduced by IEnumerable<T>.

Thanks again to Stefan for leading me to enlightenment.

Elveraelves answered 24/10, 2014 at 14:42 Comment(9)
Yeah, this is really weird. It also seems to work if i leave function GetEnumerator: IEnumerator and use method resolution only on the function IEnumerable<T>.GetEnumerator = GetEnumeratorTyped;Palladium
Well that's reasonable. As written in my answer the second method resolution clause has no real impact and I left it there for symmetry.Elveraelves
The reason is that actually the non generic GetEnumerator needs to be there for both interfaces because the generic IEnumerable<T> inherits from the non generic one. If you specify the resolution clause for IEnumerable.GetEnumerator it is missing for IEnumerable<T>.GetEnumerator: IEnumerator method (the one it "inherits" from IEnumerable). The decision to inherit the generic interface from a non generic one that operates on TObject was stupid way back then because it was blindly copied from .NET which has a rooted type system.Feud
@Stefan I don't quite get your point. I think we understand the interface inheritance. But why won't method resolution deal with it. Do we need to implement IEnumerable.GetEnumerator for both IEnumerable and IEnumerable<T>?Elveraelves
The point is that the method resolution clause for IEnumerable.GetEnumerator will only handle it for IEnumerable itself and not when you accessing it from IEnumerable<T>. But since the methods have the same name you cannot reference the non generic inherited one by IEnumerable<T>.GetEnumerator. The method resolution clause does not apply this resolution for any other interfaces that inherit from the interface type I have some method resolution clause for. That means in fact if you are implementing IEnumerable and IEnumerable<T> you have to implement 3 GetEnumerator methods.Feud
If you are naming the non generic one GetEnumerator and the generic one different and use resolution clause for it the compiler will be happy because it can use it for both interfaces but not the other way around. That is why your solution works and the other one didn't.Feud
@StefanGlienke Finally I get it. I've update the answer accordingly. Do you concur that I've understood correctly?Elveraelves
@DavidHeffernan Exactly!Feud
This was a similar discussion in a previous question, to which I added an answer #6389857 There are two issues that haven't been mentioned here - 1: T is not necessarily a TObject. IEnumerable<T> should allow you to iterate over Integers, for example. But if T is an integer, then IEnumerable cannot work. 2: If you try to iterate using the TStackoverflow<T> object, rather than the interface, GetEnumerator will be non-generic and you can only iterate TObjects.Eartha
T
9

Quick Short Answer

There are TWO interfaces groups with identical name, example: non generic IEnumerable and: IEnumerable<T>.

Overview

Have the same problem.

The problem is not interface implementation, or generic interface implementation.

There is a special situation with generic interfaces like IEnumerable<T>, because they have also a non generic IEnumerable interface. This interfaces are for compatibility with the O.S., they are not just another programming interface.

And, there are also several other generic and non generic parent interfaces, like IEnumerator or IComparable that have their own member definitions.

Solution

In order to solve the same problem, I look out for which parent interfaces, IEnumerable<T> have, and detect the matching non generic interfaces.

And, added and intermediate non generic class that implemented the non generic interfaces. This allow me to easily detect which interfaces & matching member definitions where missing.

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;

And things, are more difficult, because there are several members overloaded with the same identifier, but different parameter types or different return types, depending, if they are from the generic interface or teh non-generic interface.

The solution by @David Heffernan where there is only a single class, is not bad, I did try the same approach, but it was more comples.

Therefore, I applied the intermediate class, because it allowed me to find out, which interfaces & respective members, where missing.

Cheers.

Tedmann answered 24/10, 2014 at 19:54 Comment(2)
The cool thing about implementing it like this is that you don't need the resolution clause at all. So you can name both methods GetEnumerator and be fine.Feud
FWIW, this isn't really anything to do with generics. That can be seen from the example in my answer.Elveraelves
B
0

Actually, it's very simple, although not really obvious. Key is to know that the Compiler does some magic with enumerators.

First, we don't need to implement IEnumerator or IEnumerator<T> at all. Instead, we declare or own interface. For the sake of this sample we are enumerating WideStrings, so it could look like below. Note that the method/property names and signatures are important here:

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

Now, the surrounding class gets a method called GetEnumerator which returns something that looks like an enumerator.

I deliberately wrote "something that looks like an enumerator", not "something that implements IEnumerator<T>", because that's all the compiler needs to let the magic happen:

function  GetEnumerator : IFoobarEnumerator;  

Aside from the fact that we of course still need to implement the enumerator class for IFoobarEnumerator - that's it. We're done.

Benadryl answered 8/3, 2017 at 11:28 Comment(1)
This will not support this syntax: for [element] in [enumerable class]Slantwise

© 2022 - 2024 — McMap. All rights reserved.