|
Generics with Delphi 2009 Win32
With, as a bonus, anonymous routines and routine references
Date de publication : November 13th, 2008 II. Daily usage: the example of TList<T> II-A. A simple code to start with II-B. Assignments between generic classes II-C. Methods of TList<T> II-D. TList<T> and comparers II-D-1. Writing a comparer using TComparer<T> II-D-2. Writing a comparer using a simple comparison function II. Daily usage: the example of TList<T>
Paradoxically, we will begin with the daily usage of a generic class -this is a misuse of language, one should say:
generic class template-, instead of the design of such a type. There are several good reasons for this.
Firstly, it is considerably easier to write a class when you have a quite precise idea on how you are going to use it.
This is more true yet when you discover a new programming paradigm.
Secondly, the majority of presentations of object oriented programming first explain how to use existing classes, as
well.
II-A. A simple code to start with
Let us begin softly. We are going to write a small program listing the squares of integer numbers from 0 to X, X
being defined by the user.
Without generics, we would have used a dynamic array (and it would have been far better, keep in mind this is a
study case), but we are going to use an integer list.
Here is the code:
What should one notice here?
First of all, of course, the declaration of the variable List, along with the creation of the instance. We have
concatenated the actual parameter (or equivalent: it is a type, not a value) to the type name TList, between
angle brackets < and >.
You can see that, in order to use a generic class, it is necessary, each time you write its name, to specify a type as
a parameter. For now, we will always use a real type there.
Some languages allow type inference, i.e., the compiler guesses the type parameter. It is not the case in Delphi
Win32. That is why you cannot write:
The second thing is more important, yet lesser noticeable, since it is actually an absence. Indeed, no cast between an
Integer and a Pointer is needed! Convenient, isn't it? And moreover, much more readable.
Another advantage is type safety: it is much better handled with generics. With casts, you always risk mistakes, e.g.
adding a pointer to list intended for integers, or vice versa. If you correctly use generics, you will not probably
need casts any more, or so few. Thereby, you will get less chances to make mistakes. Moreover, if you try and add a
Pointer to an object of class TList<Integer>, the compiler will refuse your code. Before generics,
such an error could have been revealed only by an absurd value, maybe months after releasing the software into
production.
Note I took the type Integer on purpose, to limit code that is not directly related to the notion of generics, but
one could have use any type instead.
II-B. Assignments between generic classes
As you know, when speaking of inheritance, we can assign an instance of a child class to a variable of a parent class,
but not the other way. What about it with generics?
Start from the principle that you cannot do anything! Every following examples are invalid, and do not compile:
As the comment implies, despite the fact that a TComponent may be assigned to a TObject, a
TList<TComponent> may not be assigned to a TList<TObject>. In order to understand why, just
think that TList<TObject>.Add(Value: TObject) would allow, if the assignment was valid, to insert a
TObject value in a TComponent list!
The other important thing to notice is that TList<T> is not a specialization of TList,
neither a generalization. Actually, they are totally different types, the former one being declared in the
Generics.Collections unit, and the latter in Classes!
II-C. Methods of TList<T>
Interrestingly, TList<T> does not provide the same set of methods as TList does. We have more
"high-level" methods at our disposal, but less "low-level" methods (as Last, which is missing).
The new methods are the following:
I draw your attention to those changes, which you may find insignificant, because they are quite characteristic of the
general changes in design concerns braught by generics.
What is the purpose, indeed, of implementing an AddRange method in the from now on obsolete TList class?
None, since every item would have been cast, in turn. Therefore, one would have written a loop anyway, in order to build
the array to insert. One might as well call Add in each loop iteration.
Meanwhile, with generics, the code can be written once and for all, and it becomes truly useful, for each and every
type.
What you should remark and understand here, is that generics allow much more factoring of behaviors.
II-D. TList<T> and comparers
Certainly, TList<T> can handle any type. But how can it know how to compare two elements? How to know if
they are equal, in order to search with IndexOf? How to know if one is lesser than another, in order to sort the
list?
The answer is comparers. A comparer is an interface of type IComparer<T>. So yes, we are staying in
the middle of generics. This type is declared in Generics.Defaults.pas.
When you instanciate a TList<T>, you may pass a comparer to the constructor, which will be used by all
methods that need it. If you do not, a default comparer will be used.
The default comparer depends on the element type, of course. To get it, TList<T> calls the class method
TComparer<T>.Default. This method does some nasty work, based on RTTI, to get the best possible solution.
But does not always fit the requirements.
You may use the default comparer for the following data types:
For all other types, the default comparer compares only the memory contents of the variable. One should therefore write
a custom comparer.
There exist two simple ways. The first one is based on writing a function, the other one on the derivation of the class
TComparer<T>. We will illustrate both of them by implementing comparison for TPoint. We will
assume that points are compared according to their distance to the origin -the point (0, 0)- in order to have a total
ordering (in the mathematical meaning).
II-D-1. Writing a comparer using TComparer<T>
Nothing easier, you have always done that! An only method to override: Compare. It must return 0 on equality, a
positive integer if the first parameter is greater than the second one, and a negative integer otherwise.
Here is the result:
In order to use our comparer, just instantiate it and pass the instance to the list constructor. Here is a small program
that creates 10 random points, sort them and prints the sorted list.
II-D-2. Writing a comparer using a simple comparison function
This alternative seems to be simpler, given its name: no need to play with additional classes. However, I have chosen to
present it second, because it introduces a new data type available in Delphi 2009. I am speaking of routine
references.
Er ... I know routine references! Hum, well, no, you do not ;-) What you already know are procedural types, for
example TNotifyEvent:
Routine reference types are declared, in this example, like TComparison<T>:
There are at least three differences between procedural types and routine reference types.
Firstly, a routine reference type cannot be marked as of object. In other words, you can never assign a method to
a routine reference, only... routines. (Or, at least, I have not been successful in trying to do so ^^.)
The second difference is more fundamental: while a procedural type (non of object) is a pointer to the base
address of a routine (its entry point), a routine reference type is actually an interface! With reference
counting and this kind of things. However, you probably will never have to care about that, because its daily usage is
identical to that of a procedural type.
Lastly -and this explains the apparition of routine references-, one can assign a anonymous routine (we will see
in a moment what it is like)- to a routine reference, but not to a procedural type. Try to do so, you will soon see that
the compiler reports errors. Incidentally, that explains also why routine references are implemented as interfaces, but
thoughts on this subject are not within the framework of this tutorial.
Let us get back to our point sorting. In order to create a comparer on the basis of a function, we use another class
method of TComparer<T>; it is Construct. This class method takes as parameter a routine reference of
type TComparison<T>. As was already said, using routine references is quite similar to the use of
procedural types: one may use the routine name as parameter, directly. Here is the code:
The only difference coming, of course, from the creation of the comparer. The rest of the usage of the list is strictly
identical (just as well!).
Internally, the class method Construct creates an instance of TDelegatedComparer<T>, which takes as
parameter of its constructor the routine reference which is going to handle the comparison. Calling Construct
thus returns an object of this type, encapsulated in an interface IComparer<T>.
Well, it was finally easier as well. Actually, on should realize it: generics are there to ease our life!
But I have let something go: one can assign a anonymous routine to a routine reference. So, let us see what would it be
like:
This kind of create is interresting mostly if it is the only place where you need the comparison routine.
Interresting, isn't it?
Voilà! Here is the end of our small tour of comparers used with TList<T>, and, with it, this introduction to
generics through the use of this class. In the following chapter, we will begin to learn how one can write his own
generic class.
Warning: include(): http:// wrapper is disabled in the server configuration by allow_url_include=0 in /home/developpez/www/developpez-com/upload/sjrd/delphi/tutoriel/generics/index.php on line 41 Warning: include(http://sjrd.developpez.com/references.inc): failed to open stream: no suitable wrapper could be found in /home/developpez/www/developpez-com/upload/sjrd/delphi/tutoriel/generics/index.php on line 41 Warning: include(): Failed opening 'http://sjrd.developpez.com/references.inc' for inclusion (include_path='.:/opt/php56/lib/php') in /home/developpez/www/developpez-com/upload/sjrd/delphi/tutoriel/generics/index.php on line 41 |
Copyright © 2008-2009 Sébastien Doeraene. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.