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:
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.
IEnumerable interface is a generic interface which allows looping over generic or non-generic lists. IEnumerable interface also works with linq query expression. IEnumerable interface Returns an enumerator that iterates through the collection.
IEnumerable is an interface defining a single method GetEnumerator() that returns an IEnumerator interface. It is the base interface for all non-generic collections that can be enumerated. This works for read-only access to a collection that implements that IEnumerable can be used with a foreach statement.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With