I have an existing class, that has an existing method, that allows you to pass it a list of stuff:
TContoso = class(TSkyrim)
public
procedure AddObjects(Objects: TList);
end;
And so, in the before-times, someone could pass a TList
or a TObjectList
to the method:
var
list: TList;
list := TObjectList.Create(True);
contoso.AddObjects(list);
It didn't matter, as TObjectList
was a TList
. My method was flexible; it could take either.
Now in the after times
Now in the after times, i prefer typed lists:
var
list: TList<TGrobber>;
list := TObjectList<TGrobber>.Create(True);
contoso.AddObjects(list);
Of course that doesn't compile, as neither TList<T>
nor TObjectList<T>
descend from TList
. Which isn't such a problem. I intuitiavely understand that i don't actually need a TList
, i just need something that is "enumerable":
Based on my experience in the .NET FCL, that means i simply need to declare the parameter is IEnumerable
, because everything is enumerable:
IEnumerable<T>
comes fromIEnumerable
ICollection
comes fromIEnumerable
ICollection<T>
comes fromIEnumerable
IList
comes fromIEnumerable
IList<T>
comes fromIEnumerable
List
comes fromIEnumerable
List<T>
comes fromIEnumerable
So i would do something like:
TContoso = class(TSkyrim)
public
procedure AddObjects(Objects: IEnumerable);
end;
Except the Delphi BCL doesn't allow the polymorphism that .NET world allows; the things that are enumerable don't implement the IEnumerable
interface:
TList = class(TObject)
public
function GetEnumerator: TListEnumerator;
end;
TObjectList = class(TList);
TList<T> = class(TEnumerable<T>)
public
function GetEnumerator: TEnumerator<T>;
end;
TObjectList<T> = class(TList<T>);
Without the typing, how does the compiler know a type is enumerable?
Delphi uses secret hard-coded magic.:
the class or interface must implement a prescribed collection pattern. A type that implements the collection pattern must have the following attributes: - The class or interface must contain a public instance method called
GetEnumerator()
. TheGetEnumerator()
method must return a class, interface, or record type. The class, interface, or record returned byGetEnumerator()
must contain a public instance method calledMoveNext()
. TheMoveNext()
method must return a Boolean. - The class, interface, or record returned byGetEnumerator()
must contain a public instance, read-only property calledCurrent
. The type of theCurrent
property must be the type contained in the collection.
What is the way that the language designers intended me to use enumerables in my code?
- What do i declare the type of paramater
- how do i check for the presence of a method called
GetEnumerator
? - how do i call the method
GetEnumerator
? - how do i call the
Current
property? - how do i call the
Next
method?
For example:
TContoso = class(TSkyrim)
public
procedure AddObjects(const Objects);
end;
procedure TContoso.AddObjects(const Objects);
var
o: TObject;
enumerator: TObject;
bRes: Boolean;
begin
//for o in Objects do
// InternalAdd(nil, '', o);
if not HasMethod(Objects, 'GetEnumerator') then
Exit;
enumerator := InvokeMethod(Objects, 'GetEnumerator');
if not HasMethod(enumerator, 'MoveNext') then
Exit;
bRes := InvokeMethod(enumerator, 'MoveNext');
while bRes do
begin
if HasMethod(enumerator, 'Current');
InternallAdd(nil, '', InvokeMethod(enumerator, 'Current'));
bRes := InvokeMethod(enumerator, 'MoveNext');
end;
end;
What is the intended way to pass "an enumerable bag of stuff"?
Hack
TContoso = class(TSkyrim)
public
procedure AddObjects(Objects: TList); overload;
procedure AddObjects(Objects: TList<T>); overload;
end;
There must be a reason the designers chose not to have IList
implement IEnumerable
. There must be a compile time mechanism to iterate a list. But what is that reason, and what is that way.
AsEnumerable
method to the containers. But a better library (Spring) would come with what you need out of the box I suspect. – Ternan