delphigenericsdelphi-xe6

How to implement IEnumerable<T>?


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.

Solution

  • 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.