Notice: Undefined index: HTTPS in /home/developpez/www/developpez-com/template/entete.php on line 28
Generics with Delphi 2009 Win32
Developpez.com - Delphi
X

Choisissez d'abord la catégorieensuite la rubrique :

Generics with Delphi 2009 Win32

With, as a bonus, anonymous routines and routine references

Date de publication : November 13th, 2008


VIII. Generic methods
VIII-A. A generic Min function
VIII-B. Overloading and constraints
VIII-C. Some extras for TList<T>


VIII. Generic methods

We have explored so far the many possibilities offered by generics, applied to types defined by the developer. It is time to move on to generic methods.

In many presentations of generics and templates in other languages, this use of generics is explained first. Again, I have preferred to begin with generic types, which are used more often.


VIII-A. A generic Min function

In order to introduce the concept, we will write a class method TArrayUtils.Min<T>, which finds and returns the smallest element of an array. We will then, of course, need a comparer of type IComparer<T>.

Like a generic type name, a generic method name must be followed by the generic parameters, between angle brackets. Here, the generic type is the type of the elements of the array.

type
  TArrayUtils = class
  public
    class function Min<T>(const Items: array of T;
      const Comparer: IComparer<T>): T; static;
  end;
				
warning No, it is not possible to declare a global routine with generic parameters. A possible reason for this limitation is due to a parallelism with the Delphi.NET syntax, in order to reduce development and maintenance efforts, internally.
idea To make up for this limitation, we then use class methods. Better declared as static. This has nothing to do with the homonym keyword in C++. It has to do here with static linking. This is to say, in such a method, there is no Self, and then calls to virtual class methods, or to virtual constructors, are to "virtualised". In other words, it is like they were not virtual any more.
Finally, it results in a genuine global routine, but with a different namespace.
info Unlike some other languages, it is not necessary for a parameter at least to use the generic type. Thus, it is possible to write a method Dummy<T>(Int: Integer): Integer, which has no formal paremeter whose type depends on the generic parameter T. In C++, for example, that would not get pass compilation.
The implementation of the method is quite similar to classes. You have to repeat the angle brackets with the generic types, but not their possible constraints. Our Min method will then look like this:

class function TArrayUtils.Min<T>(const Items: array of T;
  const Comparer: IComparer<T>): T;
var
  I: Integer;
begin
  if Length(Items) = 0 then
    raise Exception.Create('No items in the array');

  Result := Items[Low(Items)];
  for I := Low(Items)+1 to High(Items) do
    if Comparer.Compare(Items[I], Result) < 0 then
      Result := Items[I];
end;
				
Nothing exceptional, as you may see ;-)


VIII-B. Overloading and constraints

Let us take advantage of this nice example to review our constraints, and provide an overloaded version for those element types which support the IComparable<T> interface (this interface is declared in System.pas).

Before that, let us add another overload which takes a routine reference of type TComparison<T>. Remember we can easily "convert" a TComparison<T> call-back to a IComparer<T> interface with the TComparer<T>.Construct method.

You may observe the usage of the method CompareTo on the Left parameter. This call is of course permitted only because of the constraint.

type
  TArrayUtils = class
  public
    class function Min<T>(const Items: array of T;
      const Comparer: IComparer<T>): T; overload; static;
    class function Min<T>(const Items: array of T;
      const Comparison: TComparison<T>): T; overload; static;
    class function Min<T: IComparable<T>>(
      const Items: array of T): T; overload; static;
  end;

class function TArrayUtils.Min<T>(const Items: array of T;
  const Comparison: TComparison<T>): T;
begin
  Result := Min<T>(Items, TComparer<T>.Construct(Comparison));
end;

class function TArrayUtils.Min<T>(const Items: array of T): T;
var
  Comparison: TComparison<T>;
begin
  Comparison :=
    function(const Left, Right: T): Integer
    begin
      Result := Left.CompareTo(Right);
    end;
  Result := Min<T>(Items, Comparison);
end;
				
info Have a look at the call to Min<T>: specifying the actual type between angle brackets is mandatory even there. This is not the case in other languages like C++.
Now, we want to provide a fourth overloaded method, again only with the Items parameter, but with an unconstrained T parameter. This version would use TComparer<T>.Default.

But this is impossible! Indeed, even though the constraints on types do change, the parameters (arguments) are still the same. So, the two overloads are totally ambiguous! Therefore, the following declaration would result in a compiler error:

type
  TArrayUtils = class
  public
    class function Min<T>(const Items: array of T;
      const Comparer: IComparer<T>): T; overload; static;
    class function Min<T>(const Items: array of T;
      const Comparison: TComparison<T>): T; overload; static;
    class function Min<T: IComparable<T>>(
      const Items: array of T): T; overload; static;
    class function Min<T>(
      const Items: array of T): T; overload; static; // Compiler error
  end;
				
Then, we must choice: abandon either the first or the second, or else use another name. And as, until base types like Integer support the IComparable<T> interface, you will need both as often, you will have to opt for another name ;-)

type
  TArrayUtils = class
  public
    class function Min<T>(const Items: array of T;
      const Comparer: IComparer<T>): T; overload; static;
    class function Min<T>(const Items: array of T;
      const Comparison: TComparison<T>): T; overload; static;
    class function Min<T: IComparable<T>>(
      const Items: array of T): T; overload; static;

    class function MinDefault<T>(
      const Items: array of T): T; static;
  end;

class function TArrayUtils.MinDefault<T>(const Items: array of T): T;
begin
  Result := Min<T>(Items, IComparer<T>(TComparer<T>.Default));
end;
				
Why did I cast explicitly Default into an IComparer<T>, though it is obviously already an IComparer<T>? Because routine references and overloads still do not work very well with each other, and the compiler seems to get confused with the mixing. Without the cast, the compilation fails ...


VIII-C. Some extras for TList<T>

Even though the TList<T> is quite a good innovation, it could nevertheless provide some more practical methods.

Here is, for example, an implementation of the .NET FindAll method for TList<T>. This method aims to select a sub-list from a predicate function. What we call a predicate function is a call-back routine that takes an element of the list, and returns True if it should be selected, False otherwise. We define a routine reference type TPredicate<T> as follows:

unit Generics.CollectionsEx;

interface

uses
  Generics.Collections;

type
  TPredicate<T> = reference to function(const Value: T): Boolean;
				
Then, since it is unfortunately impossible to write a class helper for a generic class, we will write a class method FindAll<T> which is going to to that. Since we are deprived of class helper, we will at least take advantage of it to be more general, working with an enumerator, or an enumerable.

type
  TListEx = class
  public
    class procedure FindAll<T>(Source: TEnumerator<T>; Dest: TList<T>;
      const Predicate: TPredicate<T>); overload; static;
    class procedure FindAll<T>(Source: TEnumerable<T>; Dest: TList<T>;
      const Predicate: TPredicate<T>); overload; static;
  end;

implementation

class procedure TListEx.FindAll<T>(Source: TEnumerator<T>; Dest: TList<T>;
  const Predicate: TPredicate<T>);
begin
  while Source.MoveNext do
  begin
    if Predicate(Source.Current) then
      Dest.Add(Source.Current);
  end;
end;

class procedure TListEx.FindAll<T>(Source: TEnumerable<T>; Dest: TList<T>;
  const Predicate: TPredicate<T>);
begin
  FindAll<T>(Source.GetEnumerator, Dest, Predicate);
end;

end.
				
One can use this method as follows:

Source := TList<Integer>.Create;
try
  Source.AddRange([2, -9, -5, 50, 4, -3, 7]);
  Dest := TList<Integer>.Create;
  try
    TListEx.FindAll<Integer>(Source, Dest, TPredicate<Integer>(
      function(const Value: Integer): Boolean
      begin
        Result := Value > 0;
      end));

    for Value in Dest do
      WriteLn(Value);
  finally
    Dest.Free;
  end;
finally
  Source.Free;
end;
				
info Again, the cast is necessary because of overloads. This is quite deplorable, but yet it is so. If you prefer not to have casts, use different names, or get rid of one of the two overloads.
It is up to you to complement this class with other methods like FindAll :-)

 
Tutoriels
Les génériques avec Delphi 2009 Win32 (English version) - également disponible en espagnol et en russe
Réaliser un plug-in comportant un composant
Construire une procédure pointant sur une méthode
Création de composants - en 4 parties
Refactoring avec Delphi 2007
Prise en main de Delphi 2005
Analyseurs syntaxiques - Leur fonctionnement par l'exemple
Créer un fichier d'aide HLP
Pourquoi un paramètre const change-t-il mystérieusement de valeur ?
Sources
SJRDUnits - Routines et classes diverses
SJRDComps - Quelques composants
Projet Sepi
Présentation
FAQ Sepi
Programmes
FunLabyrinthe - Jeu de labyrinthe très spécial et très fun
TrickTakingGame - Jeux de cartes à plis en ligne
MultiAgenda - Agenda multi-répertoires
DecodeFormulaires - Décode les formulaires
Excel --> HTML - Convertisseur de tableaux Excel en HTML
AddressLinks - Lie les adresses Internet et e-mail d'un document HTML
Vipion - Tic Tac Toe sur 4x4 cases avec jeu de l'ordinateur
BigCalc - Calculatrice de haut niveau
Espace paroissial Astérion de Watermael-Boitsfort

Valid XHTML 1.0 TransitionalValid CSS!


Notice: Undefined index: HTTPS in /home/developpez/www/developpez-com/template/pied.php on line 5

Copyright © 2008-2009 Sébastien Doeraene. Aucune reproduction, même partielle, ne peut être faite de ce site et 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.

Responsables bénévoles de la rubrique Delphi : Gilles Vasseur - Alcatîz -