How do i implement IEnumerable<T>
?
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.
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
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
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
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:
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.
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.