|
Generics with Delphi 2009 Win32
With, as a bonus, anonymous routines and routine references
Date de publication : November 13th, 2008 III. Design of a generic class III-A. Class declaration III-B. Implementing a method III-C. The pseudo-routine Default III-D. Routine references with tree walks III-D-1. Other methods using anonymous routines? III-E. And the rest III-F. How to use TTreeNode<T> ? III. Design of a generic class
Now that we know how one can use generic classes, it is time to see how does a generic class work internally.
Therefore, we are going to develop a TTreeNode<T> class, which is going to be a generic implementation of a
tree. This time, it will not be a study case any more. It is a real class, which you will be able to use in your real
projects.
III-A. Class declaration
Let us begin with the beginning: the class declaration. As you may recall from the extract of the declaration of
TList<T>, the generic parameter (traditionally called T when it has no precise meaning) is added
between angle brackets. On should then write:
A tree node will be labelled by a value of type T, and will have 0 to many children. When a node is destroyed, it
frees all its children.
A value of type T? Well, this is as simple as this: FValue: T; the same way as for any normal type. To
keep a list of the children, we will use a TObjectList<U>, declared in Generics.Collections. I have
used U on purpose here, because I do not want you to be misleaded. Actually, U will be set to
TTreeNode<T>! So yes, one can use a generic class as an actual generic parameter.
Our class will then have the following fields:
Weird? If we think a bit about it, not that much. We have said previously that a generic type could be replaced by
any type. So, why not a generic class type?
As methods, besides the constructor and destructor, we provide methods for in-depth walks (preorder and postorder),
along with methods for adding/moving/deleting child nodes. Actually, the addition will be asked by the child itself,
when it will be given its parent.
For the walks, we will need a call-back type. Guess what? We will use a routine reference type. And we will provide two
overloads for each walk: a walk on the nodes, and a walk on the values of the nodes. The second one will probably be
used more often when using the TTreeNode<T> class. The first one is provided for completeness, and is more
general. It will be used by several methods of TTreeNode<T> (e.g. the second overload).
Finally, there will be, of course, properties accessing the parent, children and labelled value. Which leads to the
code below. You may see there is nothing new, besides the fact there are many T's everywhere.
Here is the complete declaration of the class -which, be reassured, I give you after I have completely impleted the
class ^^.
Let us identify what is remarkable here. Actually, not much, beside the fact that every parameter of type T is
declared as const. Indeed, we do not know, at the time of writing, what could be the actual T. And thus
there are some possibilities for it being a "heavy" type (as a string or record), for which it is more efficient to use
const. And, as const is never penalizing (it is effectless on "light" types, such as Integer), we always
put it when working with generic types.
However, when a parameter is of type TTreeNode<T>, we do not put const. Indeed, no matter what is
T, TTreeNode<T> will remain a class type, which is a light type.
III-B. Implementing a method
In order to illustrate the particularities of the implementation of generic method, let us work with GetRootNode,
which is very easy. It is written as below:
The only thing to notice here is that, each time your write the implementation of a generic method, you must
specify the <T> next to the class name. It is mandatory, indeed, because TTreeNode (without <T>)
would be another type, maybe existing in the same unit.
III-C. The pseudo-routine Default
In order to implement the two constructors that have no AValue parameter, we will need to initialize FValue
one way or the other. Sure but, since we do not know the actual type of the value, how can we write a default value, for
any possible type?
The solution is the new pseudo-routine Default. As well as TypeInfo, this pseudo-routine takes, as
parameter, a type identifier. And the name of a generic type is actually a type identifier.
This pseudo-routine "returns" the default value of the provided type. One can then write:
III-D. Routine references with tree walks
In order to illustrate routine references, from the other side of the mirror, let us discuss the implementation of tree
walks.
Both overloads of the walk takes as parameter a call-back routine reference. Notice that, also here, we have used a
const parameter. Indeed, the attentive reader will remeber that routine references are actually interfaces. Now,
an interface is a heavy type. One should then use const when possible.
Except this little consideration, there is nothing special to say on the first version, the one on nodes:
To call the call-back, we have written exactly the same piece of code as with procedural types, i.e. the same form as a
routine call, but with the routine reference variable instead of the routine name.
When implementing the second overload, we use the first one, giving for Action... a anonymous routine!
Isn't it a nice piece of code?
III-D-1. Other methods using anonymous routines?
Yes, two others. DoAncestorChanged, which has to undertake a preorder walk on the method AncestorChanged.
And BeforeDestruction, which has to do a preorder walk on the Destroying method of all the children. It is
always the same:
III-E. And the rest
The rest, well ... That is pretty classic ;-) Nothing new. I will not extend on this, but the complete source is available
for download, as all the others,
at the end of the tutorial.
III-F. How to use TTreeNode<T> ?
That is certainly nice to write a generic tree class, but can we use it?
After a description in every detail of the TList<T> class, there is not much to say left. I am only going
to give you the code of a small test program.
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.