Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the intended way to pass a list?

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 from IEnumerable
  • ICollection comes from IEnumerable
  • ICollection<T> comes from IEnumerable
  • IList comes from IEnumerable
  • IList<T> comes from IEnumerable
  • List comes from IEnumerable
  • List<T> comes from IEnumerable

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(). The GetEnumerator() method must return a class, interface, or record type. The class, interface, or record returned by GetEnumerator() must contain a public instance method called MoveNext(). The MoveNext() method must return a Boolean. - The class, interface, or record returned by GetEnumerator() must contain a public instance, read-only property called Current. The type of the Current 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.

like image 208
Ian Boyd Avatar asked Nov 02 '14 16:11

Ian Boyd


2 Answers

TObjectList<T> derives from TList<T>, so use that as your parameter, making the method itself a Generic if you need to support multiple object types in the list, and then use a for-in loop to enumerate the list (which also works for the non-Generic TList and various other container classes):

Iteration Over Containers Using For statements

Type
  TContoso = class(TSkyrim)
  public
    procedure AddObjects(Objects: TList); overload;
    procedure AddObjects<T: class>(Objects: TList<T>); overload;
  end;

procedure TContoso.AddObjects(Objects: TList);
var
  Obj: Pointer;
begin
  for Obj in Objects do
  begin
    // use TObject(Obj) as needed...
  end;
end;

procedure TContoso.AddObjects<T>(Objects: TList<T>);
var
  Obj: T;
begin
  for Obj in Objects do
  begin
    // use Obj as needed...
  end;
end;

var
   list: TList;

list := TObjectList.Create(True);
contoso.AddObjects(list);

var
  list: TList<TGrobber>;

list := TObjectList<TGrobber>.Create(True);
contoso.AddObjects<TGrobber>(list);

Let the compiler validate the presence of GetEnumerator() and the sub-methods of the returned enumerator class. Don't try to handle it manually (if you want to do that, you have to use RTTI for it). Besides, for-in loops have built-in support for other types of containers (arrays, strings, sets, and records) that do not expose GetEnumerator() but are otherwise enumerable.

like image 177
Remy Lebeau Avatar answered Sep 27 '22 17:09

Remy Lebeau


Obviously the classic VMT (Virtual Method Table) structure didn't support multiple inheritance, so they didn't want anything enumerable to have to descent from say a TEnumerable that was descending from TObject since that would be too restrictive.

So they used that hack with the specific method signature that had to be in your class, forcing you to use the HasMethod/InvokeMethod. So indeed your methods that get passed anything "enumerable" would then be of the form "procedure TContoso.AddObjects(const Objects)"

like image 35
George Birbilis Avatar answered Sep 27 '22 18:09

George Birbilis