|
Generics with Delphi 2009 Win32
With, as a bonus, anonymous routines and routine references
Date de publication : November 13th, 2008 V. Contraints on generic types V-A. What are the available constraints? V-B. But what is the purpose of it? V-C. A variant with constructor VI. Parameterizing with more than on type VII. Other generic types V. Contraints on generic types
OK, you now know the basis. It is time to move on to serious things, namely, constraints.
As the name implies, constraints allow to impose some restrictions on the actual types that can be used to parameterize
a generic type. Continuing the comparison with method parameters: a constraint is to the type what a type is to a
variable. Not clear? OK, when you specify a type for a parameter, you may only pass it an expression compatible with
this type. When you specify a constraint on a type parameter, you must replace it by an actual type satisfying this
constraint.
V-A. What are the available constraints?
There are few available constraints. Actually, there are four kinds of constraints.
On may force a generic type to be:
In order to impose a constraint to a generic parameter, one should write:
Those syntaxes impose, respectively, that T must be replaced by the TStream class or one of its
descendants; by IInterfaceList or one of its descendants; by an ordinal, floating point or record type (a
non-null data type, following the terminology of Delphi); or finally by a class type providing a zero-argument
constructor.
It is possible to combine several interface constraints, or a class constraint along with one or several interface
constraints. In this case, the actual type must satisfy all the constraints at the same time. The
constructor constraint may also be combined with class and/or interface constraints. It is even possible to
combine the record constraint with one or several interface constraint, yet I cannot see an actual type that could
statisfy the two constraints at once (in .NET, it is possible, but, for now, not in Win32)!
It is also possible to use a generic class or interface as constraint, with a specified type parameter. This parameter
could by T itself. For example, one might demand a type which can compare to itself. One would write:
Additionnaly, if you want T to be a class type, you may combine:
V-B. But what is the purpose of it?
"I thought generic were designed to write a code once and for all for all types. Then, what is the interest of
limit the possible types?"
Well, it allows the compiler to get more knowledge about the type used. For example, it allows it to know, with a
constraint <T: class>, that it is legal to call the method Free to a variable of type T. Or, with a
constraint <T: IComparable<T>>, that one may write Left.CompareTo(Right).
We are going to illustrate that with a child class of TTreeNode<T>, called
TObjectTreeNode<T: class>. Following the example of TObjectList<T: class>, which provides an
automatic freeing of its elements when destroyed, our class will release its labelled value on destruction.
Actually, there is not much code, considering we have already written most of it in the superclass.
That is all. The aim of this example is only to show the technique. Not to have an exceptionnal code.
You should notice two things here. First, a generic class may inherit from another generic class, using again the same
generic parameter (or not).
Then, in the implementation of methods from generic classes with constraints, the constraints must not be repeated.
You may, by the way, try and delete the constraint and compile. The compiler will raise an error on the call to
Free. Indeed, Free is not available on all types. But it is on all class types.
V-C. A variant with constructor
You might as well want the two constructors without the AValue parameter to create an object for FValue
instead of using Default(T) (the latter returning nil here because T is constrained to be a class).
The constructor constrained is intended for this purpose, which yields:
Again, if you remove the constructor constraint, you will get a compiler error on T.Create.
VI. Parameterizing with more than on type
As you might have already thought, it is possible to parameterize a generic type with several types. Each of them having
its own set of constraints.
In this way, the TDictionary<TKey,TValue> class takes two type parameters. The first one being the type of
the keys, and the second one being the type of the elements. This class implements a hash table.
The declaration syntax is quite lax, on this point. It is indeed possible to separate the types with commas (,) or
semi-colons (;). Even mixing is accepted when there are more than two types. As much at the declaration level as at the
implementation level. However, when using a generic type, you must use commas!
This said, if you place one or more constraints on a type which is not the last one, you will have to use a semi-colon to
separate it from the next one. Indeed, the comma would mean an additional constraint.
So, let me suggest you a style rule -which is not the one followed by Embarcadero. Always use the semi-colons in the
declaration of generic types (where it is liable to have a constraint) and use commas everywhere else (implementation of
methods, and usage of generic types).
As I have no better example of generic type with two parameters to offer you than TDictionary<TKey,TValue>,
I suggest you have a look at the code of this class (defined, as you might have guessed, in
Generics.Collections). Here is an extract of it:
VII. Other generic types
Until now, we have only defined generic classes and records. Yet, we have already crossed over some generic
interfaces.
It is also possible to declare generic array types (static or dynamic), where only the elements type may
depend on the generic type (not the dimensions). But it is quite unlikely that you will find a practical situation for
them.
So, it is not possible to declare a generic pointer type, nor a generic set type:
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.