What is the intended way to pass the list?

I have an existing class that has an existing method that allows you to pass a list of things to it:

TContoso = class(TSkyrim) public procedure AddObjects(Objects: TList); end; 

So, in the previous moments, someone could pass a TList or TObjectList method to a method:

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

It didn't matter, because TObjectList was a TList . My method was flexible; he can accept and.

Now at times

Now in the following moments I prefer printed lists:

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

Of course, this does not compile, since neither TList<T> nor TObjectList<T> come with TList . This is not such a problem. I intuitively understand that I really don't need a TList , I just need something "enumerated":

Based on my experience with .NET FCL, this means that I just need to declare an IEnumerable parameter, 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 that Delphi BCL does not allow the polymorphism that the .NET world allows; things that are listed do not 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 input, how does the compiler know the type, can I list it?

Delphi uses secret encoded magic. :

the class or interface must implement the prescribed collection template. A type that implements a collection template must have the following attributes: - The class or interface must contain an open instance method called GetEnumerator() . The GetEnumerator() method should return a class, interface, or record type. The class, interface, or record returned by GetEnumerator() must contain an open instance method named MoveNext() . The MoveNext() method should return a Boolean . - The class, interface, or record returned by GetEnumerator() must contain a public instance, a read-only property called Current . The type of the Current property must be the type contained in the collection.

How did the language developers intend to use enums in my code?

  • What to declare paramater type
  • How to check for a GetEnumerator method?
  • How to call the GetEnumerator method?
  • How can I name 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 convey an "enumerable bag of things"?

Hack

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

There must be a reason designers decided not to have IList implement IEnumerable . To enumerate the list, a compilation mechanism must exist. But what is the reason, and what is it.

+7
delphi delphi-xe6
source share
2 answers

TObjectList<T> comes from TList<T> , so use this as your parameter, making this method common in itself if you need to support several types of objects in a list and then use it for a loop to list the list (which also works for non-Generic TList and other container classes):

Iterate over containers Use for claims

 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 check for GetEnumerator() and sub-methods of the return class of the enumerator. Do not try to process it manually (if you want to do this, you must use RTTI for it). In addition, 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 enumerated.

+1
source share

Obviously, the classic VMT structure (virtual method table) did not support multiple inheritance, so they didn’t want to list anything to go down, say, TEnumerable, which went down from TObject, because that would be too restrictive.

So they used this hack with the specific method signature that should have been in your class, forcing you to use the HasMethod / InvokeMethod method. So, really, your methods that pass something "enumerated" will then take the form "TContoso.AddObjects (const Objects) procedure"

0
source share

All Articles