Warning: filemtime(): stat failed for /home/developpez/www/developpez-com/upload/sjrdhttp://sjrd.developpez.com/stylesheet.css in /home/developpez/www/developpez-com/template/entete.php on line 405
IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

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:

  • a class type, descendant from a given class;
  • an interface type, descendant from a given interface, or a class type implementing this interface;
  • an ordinal, floating point or record type;
  • a class type that provides a zero-argument constructor.
In order to impose a constraint to a generic parameter, one should write:

type
  TStreamGenericType<T: TStream> = class
  end;

  TIntfListGenericType<T: IInterfaceList> = class
  end;

  TSimpleTypeGenericType<T: record> = class
  end;

  TConstructorGenericType<T: constructor> = class
  end;
				
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.

warning <T: class> must be used instead of <T: TObject> ... We understand that class be accepted, but we may ask why TObject is rejected.
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)!

idea Maybe -but this is pure speculation of mine- this is in anticipation of a future version, where the Integer type, for example, would "implement" the IComparable<Integer> interface. It would then statisfy both constraints of a declaration like <T: record, IComparable<T>>.
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:

type
  TSortedList<T: IComparable<T>> = class(TObject)
  end;
				
Additionnaly, if you want T to be a class type, you may combine:

type
  TSortedList<T: class, IComparable<T>> = class(TObject)
  end;
				

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.

type
  {*
    Generic tree structure whose values are objects
    When the node is destroyed, its labelled value is freed as well.
  *}
  TObjectTreeNode<T: class> = class(TTreeNode<T>)
  public
    destructor Destroy; override;
  end;

{--------------------}
{ TObjectTreeNode<T> }
{--------------------}

{*
  [@inheritDoc]
*}
destructor TObjectTreeNode<T>.Destroy;
begin
  Value.Free;
  inherited;
end;
				
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:

type
  {*
    Generic tree structure whose values are objects
    When a node is created without labelled value, a new value is created with
    the default (zero-argument) constructor of T.
    When the node is destroyed, the labelled value is freed as well.
  *}
  TCreateObjectTreeNode<T: class, constructor> = class(TObjectTreeNode<T>)
  public
    constructor Create(AParent: TTreeNode<T>); overload;
    constructor Create; overload;
  end;

implementation

{*
  [@inheritDoc]
*}
constructor TCreateObjectTreeNode<T>.Create(AParent: TTreeNode<T>);
begin
  Create(AParent, T.Create);
end;

{*
  [@inheritDoc]
*}
constructor TCreateObjectTreeNode<T>.Create;
begin
  Create(T.Create);
end;
				
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.

warning do not be mistaken: TKey and TValue are indeed (formal) generic parameters, not real types. do not mix up things because of the notation.
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:

type
  TPair<TKey,TValue> = record
    Key: TKey;
    Value: TValue;
  end;

  // Hash table using linear probing
  TDictionary<TKey,TValue> = class(TEnumerable<TPair<TKey,TValue>>)
    // ...
  public
    // ...

    procedure Add(const Key: TKey; const Value: TValue);
    procedure Remove(const Key: TKey);
    procedure Clear;

    property Items[const Key: TKey]: TValue read GetItem write SetItem; default;
    property Count: Integer read FCount;

    // ...
  end;
			
idea As you already noticed, TDictionary<TKey,TValue> uses generic type names that are more explicit than the ever-lasting T we have used so far. You should do the same, whenever the type has a particular meaning, which is the case here. All the more when there are more than one parameterized type.

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:

type
  TGenericPointer<T> = ^T; // compiler error
  TGenericSet<T> = set of T; // compiler error
			
 

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

Valid XHTML 1.0 TransitionalValid CSS!

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.