IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Les génériques avec Delphi 2009 Win32

Avec en bonus les routines anonymes et les références de routine

Les génériques avec Delphi 2009 Win32

Avec en bonus les routines anonymes et les références de routine


précédentsommairesuivant

VIII. Méthodes génériques

Nous avons exploré jusqu'ici les différentes possibilités offertes par les génériques sur les types définis par le développeur. Mais il est également possible d'écrire des méthodes génériques.

Dans beaucoup de présentations des génériques ou des templates pour d'autres langages, cette forme d'utilisation des génériques est présentée en premier. Mais encore une fois, j'ai préféré vous présenter d'abord ce qui sert souvent avant de m'intéresser aux utilisations moins fréquentes des génériques.

VIII-A. Une fonction Min générique

Pour présenter le concept, nous allons écrire une méthode de classe TArrayUtils.Min<T>, qui trouve et renvoie le plus petit élément d'un tableau. Nous aurons donc besoin d'utiliser un comparateur de type IComparer<T>.

Tout comme le nom du type devait l'être, le nom de la méthode doit être suivi des paramètres génériques entre chevrons. Ici le type générique est le type des éléments du tableau.

 
Sélectionnez
type
  TArrayUtils = class
  public
    class function Min<T>(const Items: array of T;
      const Comparer: IComparer<T>): T; static;
  end;

Et non ! Il n'est pas possible de déclarer une routine globale avec des paramètres génériques. Une raison possible viendrait du parallèlisme avec la syntaxe de Delphi.NET, afin de réduire les coûts de développement et de maintenance, en interne.

Pour pallier à ce manque, on utilise donc des méthodes de classe. Et mieux, on la précise comme étant static. Pas grand chose à voir avec le mot-clef du même nom en C++. Il s'agit ici de liaison statique. C'est-à-dire que dans une telle méthode, il n'y a pas de Self, et que donc l'appel à des méthodes de classe virtuelles, ou à des construteurs virtuels, n'est pas "virtualisé". Autrement dit, c'est comme s'il n'était pas virtuel.
Au final, cela fait de la méthode de classe statique une authentique routine globale, mais avec un espace de noms différent.

Contrairement à certains langages, il n'est pas nécessaire qu'un paramètre au moins reprenne chaque type générique introduit pour la méthode. Ainsi, il est permis d'écrire une méthode Dummy<T>(Int: Integer): Integer, qui n'a donc aucun paramètre formel dont le type est le paramètre générique T. En C++, par exemple, ça ne passerait pas.

Côté implémentation de la méthode, c'est tout à fait similaire aux classes. Il faut répéter les chevrons et les noms des types génériques, mais pas leurs contraintes éventuelles. Cela donne donc :

 
Sélectionnez
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;

Rien de bien exceptionnel donc ;-)

VIII-B. La surcharge et les contraintes

Profitons de ce bel exemple pour revoir nos contraintes, et proposer une version surchargée pour les types d'éléments qui supportent l'interface IComparable<T> (cette interface est définie dans System.pas).

Et avant ça, ajoutons une autre version surchargée qui prend une référence de routine de type TComparison<T>. Rappelez-vous qu'on peut facilement "transformer" un call-back TComparison<T> en une interface IComparer<T> avec TComparer<T>.Construct.

Vous pouvez observer l'utilisation de la méthode CompareTo sur le paramètre Left. Ceci n'est bien sûr possible que parce que, dans cette surcharge, le type T est contraint à supporter l'interface IComparable<T>.

 
Sélectionnez
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;

Remarquez l'appel à Min<T> : Il est indispensable de spécifier à l'appel aussi le (ou les) type réel utilisé. Ceci contraste avec d'autres langages comme le C++.

Maintenant, nous voulons proposer une quatrième version surchargée, toujours avec uniquement le paramètre Items, mais avec un paramètre T non contraint. Cette version devrait utiliser TComparer<T>.Default.

Mais ceci n'est pas possible ! Car, bien que les contraintes sur les types changent, les paramètres (arguments) sont les mêmes. Donc les deux versions surchargées sont totalement ambigües ! Ainsi, la déclaration supplémentaire suivante échouera à la compilation :

 
Sélectionnez
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; // Erreur de compilation
  end;

Il faut donc faire un choix : abandonner l'un ou l'autre, ou utiliser un autre nom. Et comme, jusqu'à ce que les types de base comme Integer supportent l'interface IComparable<T>, vous risquez d'utiliser aussi souvent l'un que l'autre, il va falloir opter pour l'autre nom ;-)

 
Sélectionnez
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;

Pourquoi le transtypage explicite en IComparer<T> de Default qui est pourtant manifestement déjà un IComparer<T> ? Parce que les références de routine et les surcharges ne font pas encore très bon ménage, et le compilateur a l'air de s'emmêler les pinceaux. Sans le transtypage, la compilation ne passe pas...

VIII-C. Des ajouts pour TList<T>

Si la classe TList<T> est une belle innovation, il n'en demeure pas moins qu'elle pourrait contenir plus de méthodes d'intérêt pratique.

Voici donc par exemple une implémentation de la méthode .NET FindAll pour TList<T>. Cette méthode a pour but de sélectionner une sous-liste à partir d'une fonction prédicat. Ce qu'on appelle fonction prédicat est une routine de call-back qui prend en paramètre un élément de la liste, et renvoie True s'il faut le sélectionner. On définit donc un type référence de routine TPredicate<T> comme suit :

 
Sélectionnez
unit Generics.CollectionsEx;

interface

uses
  Generics.Collections;

type
  TPredicate<T> = reference to function(const Value: T): Boolean;

Ensuite, comme malheureusement il semble impossible d'écrire un class helper pour une classe générique, nous allons écrire une méthode de classe FindAll<T> qui va faire cela. Puisqu'on est privé de class helper, on va en moins en profiter pour être plus général, et travailler sur un énumérateur quelconque, avec une surcharge pour un énumérable quelconque.

 
Sélectionnez
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.

On peut s'en servir comme ceci :

 
Sélectionnez
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;

À nouveau, le transtypage est nécessaire à cause des surcharges. C'est assez déplorable, mais c'est comme ça. Si vous préférez ne pas avoir de transtypage, utilisez des noms différents, ou débarrassez-vous d'une des deux versions.

Il ne tient qu'à vous de compléter cette classe avec d'autres méthodes de ce genre :-)


précédentsommairesuivant

Copyright © 2008 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.