Friday, March 27, 2009

Implementing IEnumerable is a nightmare

Now I know why there is no class in the VCL and RTL that implements the IEnumerable<T> or IEnumerator<T> interface. Because doing this makes you sick. Implementing an interface is more or less a very easy job. You copy all the methods from the interface declaration to your class (or you use Ctrl+Space to get a list of all the methods). And then you press Ctrl+Shift+C to generate the method body. But not with IEnumerable<T> and IEnumerator<T>. Both are derived from a non-generic interface (IEnumerable, IEnumerator) and that is where the nightmare begins.



type
IEnumerator = interface(IInterface)
function GetCurrent: TObject;
function MoveNext: Boolean;
procedure Reset;
property Current: TObject read GetCurrent;
end;

IEnumerator<T> = interface(IEnumerator)
function GetCurrent: T;
property Current: T read GetCurrent;
end;

IEnumerable = interface(IInterface)
function GetEnumerator: IEnumerator;
end;

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


If you want to implement the IEnumerable<T> interface the way you are used to, you get the following code.



type
TTest = class(TInterfacedObject, IEnumerable<TObject>)
function GetEnumerator: IEnumerator<TObject>; virtual; abstract;
end;


And if you try to compile this the compiler fails with


[DCC Error] Unit1.pas(27): E2211 Declaration of ‘GetEnumerator’ differs from declaration in interface ‘IEnumerable<System.TObject>’

System.pas(452): Related member: function GetEnumerator: IEnumerator;


That is because IEnumerator.GetEnumerator is in the way. The only solution I found (thanks to Rudy Velthuis posting in the newsgroups) was to add an additional method and rename the IEnumerable<T>.GetEnumerator method.



type
TTest = class(TInterfacedObject, IEnumerable<TObject>, IEnumerable)
function GetEnumerator: IEnumerator; virtual; abstract;
function IEnumerable<TObject>.GetEnumerator = GetGenericEnumerator;
function GetGenericEnumerator: IEnumerator<TObject>; virtual; abstract;
end;

And you have to do the same for IEnumerator<T>.GetCurrent.


My question now is why do IEnumerable<T> and IEnumerator<T> not derive from IInterface like IComparable<T>. That would make life much easier and it would prevent me from adding methods that add invalid functionality to my code. Because how shall I convert a “string” or “Integer” to a TObject that is expected by “IEnumerator.GetCurrent: TObject”.

1 comment:

Vitor Rubio said...

How do you do this with the property Current?