Developpez.com - Delphi
X

Choisissez d'abord la catégorieensuite la rubrique :


Les génériques sous Delphi .NET

Date de publication : 17/09/2007 , Date de mise à jour : 06/10/2007

Par Laurent Dardenne (Contributions)
 

Delphi 2007 alias Highlander propose comme nouveautés du langage la prise en charge des génériques. Je tiens à remercier XXX pour ses corrections orthographiques et relectures attentives.

1. Public concerné
1-1. Les sources
2. Les génériques qu'est-ce que c'est ?
3. A quoi servent-ils
4. Comment ça fonctionne ?
4-1. L'instanciation des types génériques
5. Un exemple détaillé
6. Les types de bases supportant les génériques
6-1. Les classes
6-2. Les enregistrements
6-3. Les tableaux
6-4. Les procédures et fonctions
6-4-1. Le mot clé Default
6-5. Les méthodes
6-6. Les délégués
6-7. Les interfaces
6-8. Pour information
7. Contraintes
7-1. Sur une classe ancêtre
7-2. Sur une ou plusieurs interfaces
7-3. Sur l'accés à un constructeur sans paramètre
7-4. Sur un type référence
7-5. Sur un type enregistrement
8. Héritage
8-1. Portée des paramètres de type
9. Surcharge de classe
10. Variable de classe dans les génériques
11. La classe System.Type et les génériques
12. Type Nullable
12-1. La déclaration
12-2. L'assignation
12-3. Diverses manipulations
13. Classe partielle
14. Net 3.0 et WPF
15. Liens


1. Public concerné

Testé sous le framework .NET 2.0 et Delphi 2007 beta.

Un rappel sur les nouveautés du framework dotNET 2.0.


1-1. Les sources

Les fichiers sources des différents exemples :
FTP.
HTTP.

L'article au format PDF.


2. Les génériques qu'est-ce que c'est ?

Le principe même des génériques est la réutilisation de traitements ou de structure de données sans se soucier du type de donnée manipulée. On utilisera le plus souvent les génériques sur des notions générales par exemple une liste ou une pile et moins sur une notion particulière tel qu'un tableau des jours de la semaine.

Les génériques traduisent le concept du polymorphisme paramétré.


3. A quoi servent-ils

Prenons le cas de figure d'une structure de données tel qu'une pile, elle peut contenir des entiers, des objets ou tout autre type reconnus.
Aujourd'hui sous Win32 ou dotNET 1.1 il nous faut déclarer un type TStack pour chaque type de donnée existant ou tout du moins utilisé dans nos traitements. Sous dotNET 2.0 il est désormais possible de définir une fois pour toute une classe TStack générique, ce qui nous permettra de préciser lors de son utilisation quel type on souhaite manipuler. La généricité nous offrant ici la construction de type paramètrable et permet donc de faire abstraction des types de données manipulés.

Sous .NET 2.0 l'usage des génériques supprime le boxing/unboxing et les opérations de transtypage entre les types de données, limitant ainsi les vérifications de type lors de l'exécution du programme.
Un exemple sur le forum Delphi propice à l'usage de type générique.

Voici un extrait commentée de l'unité ActnMenus :

   //Pile spécialisée afin de manipuler le type TCustomActionMenuBar;
  TMenuStack = class(TStack) 
  ...
function TMenuStack.GetBars(const Index: Integer): TCustomActionMenuBar;
begin
   //Transtypage obligatoire car 
   //l'appel de TList.Get renvoi un objet.
  Result := TCustomActionMenuBar(List[Index]); 
end;

//La pile est construite autour d'une liste d'objet
function TList.Get(Index: Integer): TObject;
begin
  Result := FList[Index];
end;

 //FList étant déclarée ainsi :
 property List: System.Collections.ArrayList read FList; 
info Le framework 2.0 propose des collections génériques dans l'espace de nom System.Collections.Generic.

4. Comment ça fonctionne ?

Projet : ..\Projet1

Etudions la déclaration d'une classe générique :

type
  TGenericType<AnyType> = class
    FData: AnyType;
    function GetData: AnyType;
    procedure SetData(Value: AnyType);
    property Data: AnyType read GetData write SetData;
  end;

function TGenericType<AnyType>.GetData: AnyType;
begin
  Result := FData;
end;

procedure TGenericType<AnyType>.SetData(Value: AnyType);
begin
  FData := Value;
end;
Ici TGenericType<AnyType> indique une classe générique, AnyType étant le paramètre de type référençant un type de donnée tel qu'Integer ou String. Chaque occurrence de AnyType présent dans cette déclaration sera, lors de la construction du type à la compilation, substituée par l'argument de type que l'on précisera de cette manière :

type
  TGenericTypeInt = TGenericType<Integer>; //Déclare un type à partir d'une classe générique
TGenericType<Integer> est appelé un type construit fermé. Integer étant appelé ici un argument de type pour AnyType.

TGenericType<AnyType> étant quant à lui un type construit ouvert tout comme TGenericAutre<V,String>, car V est un type indéterminé.

Notez que le nom du paramètre de type, utilisé dans une déclaration de générique, ne défini pas l'unicité du type :

type
  TGenericType<AnyType> = class
    FData: AnyType;
  end;

  TGenericType<ParametreDeType> = class //Erreur à la compilation
    FData: ParametreDeType;
  end;

  TGenericType<AnyType,N> = class //Surcharge Autorisée
    FData1: AnyType;
    FData2: N;
  end;
La présence de la classe génériques TGenericType<ParametreDeType> provoquera, à la compilation, l'erreur suivante :

 E2037 : La déclaration de 'TGenericType<AnyType>' diffère de la déclaration précédente
info Vous remarquerez qu'une déclaration de générique peut contenir plusieurs paramètres de type séparés par une virgule. Comme par exemple la classe du framework 2.0 Dictionary.

4-1. L'instanciation des types génériques

Extrait des en spécifications du C# 2.0 .

La première fois qu'une application crée une instance d'un type générique construit, tel qu'une pile, le compilateur just-in-time (JIT) du CLR de .NET convertis le code IL et les méta données du générique en code natif, substituant, dans le processus, les types réels aux types paramètres. Les références suivantes identiques à celle du type générique construit emploient le même code natif. Le processus de création d'un type construit spécifique à partir d'un type générique est connu sous le nom d'instanciation de type générique.

Le CLR de .NET crée une copie spécialisée du code native pour chaque instanciation de type générique de type valeur, mais partage une simple copie du code natif pour tous les types références (puisque, au niveau de code natif, les références ne sont que des pointeurs avec la même représentation).

warning Les limites actuelles sous Delphi :
- le débogueur n'évalue pas les génériques.
- le refactoring ne prend pas en charge les génériques.
- error insight ne reconnait pas les génériques.


5. Un exemple détaillé

Après avoir déclaré notre classe et ses membres, voyons la création d'une instance de notre classe paramétrée. Essayons la déclaration de variable suivante :

var
  I: TGenericType;
ici la compilation échoue en provoquant l'erreur :

 Identificateur non déclaré : ' TGenericType ' (E2003).
Pour instancier un générique il nous faut au préalable déclarer un type construit fermé qui précise l'usage de notre classe paramétrable :

type
  TGenericTypeInt = TGenericType<Integer>; // La classe générique est paramètrée avec le type Integer
Ensuite l'appel de son constructeur reste classique :

var
  I: TGenericTypeInt;
begin
  I := TGenericTypeInt.Create;
Les constructions suivantes restent possibles :

var X: TGenericType<Integer>;

begin
  X := TGenericType<Integer>.Create;
  X.Data := 100;

  With TGenericType<Integer>.Create do
   Data := 100;
Notez que les variables X et I sont compatibles en affectation.

Construisons un second type à partir de notre classe générique :
Projet : ..\Projet2


type
  TGenericTypeString = TGenericType<string>;
var
  I: TGenericTypeInt;
  D: TGenericTypeString;
begin
  I := TGenericTypeInt.Create;
  I.Data := 100;
  WriteLn(I.Data);
 
  D := TGenericTypeString.Create;
  D.Data := 'Generic';
  WriteLn(D.Data);
  I:=D;  //Erreur à la compilation
La derniére affectation, I:=D, provoque l'erreur suivante :

E2010 Types incompatibles : 'TGenericType<System.Int32>' et 'TGenericType<System.String>'
L'affectation entre différents types, construits à partir d'une même classe générique, n'est pas possible cela étant dû au typage fort des génériques.


6. Les types de bases supportant les génériques

Certains types peuvent être à la base d'une déclaration de générique, en voici la liste.


6-1. Les classes

Le type Classe, de type référence, pourra être dérivé.

  //Classe générique 
 TGenericClass<C>=Class
  FData: T;
 End;
De plus, n'importe quelle classe imbriquée dans une déclaration générique de classe ou une déclaration générique de record est elle-même une déclaration générique de classe, puisque le paramètre de type pour le type contenant doit être assuré pour créer un type construit.


6-2. Les enregistrements

Le type record, de type valeur, ne pourra être dérivé car il ne supporte pas l'héritage.

  //Record générique 
 TGenericEnregistrement<T>=Record
  Data: T;
 End;

6-3. Les tableaux

La définition de tableaux permet l'usage de paramétre de type.

type
 TGeneriqueArray<X> = array of X;
 TGeneriqueArray2D<X> = array of TGeneriqueArray<X>; // Equivalent à Array of Array of X

  //Tableau à une dimension contenant des chaînes de caractères
 TGtabString  = TGeneriqueArray<String>; 
  //Tableau à deux dimensions contenant des entiers  
 TGtab2DInt   = TGeneriqueArray2D<Integer>; 

  //Types compatibles
 TGtabInteger=TGeneriqueArray<Integer>;
 TGtabIntegerV2 = array of Integer;
Notez que les deux derniers types sont compatibles.


6-4. Les procédures et fonctions

Projet : ..\ProcedureEtFonction

Les paramètres et le type de retour d'une fonction peuvent utiliser des paramètres de type.

type
  TProcedureGenerique<A> = procedure(Param1 : A);
  TProcObjetGenerique<B> = procedure(X,Y: B) of object; //Méthode d'objet
  TFonctionGenerique<T>  = Function : T;
  
  TMaClasse = class
    procedure UneMethode<T>(X, Y: T); // Même signature que le type TProcObjetGenerique<B>
    procedure TestMethode;
    procedure TestProcedure<UnType>(Prc:TProcedureGenerique<UnType>);
    procedure TestFonction<T>(fnct: TFonctionGenerique<T>);
  end;
L'usage d'un paramètre de type dans la signature de la procédure TestProcedure permet de le propager afin de déclarer le type de l'argument nommé Prc. Sinon la présence seul d'un argument de type dans la signature d'une méthode

  TMaClasse = class
    ...
    procedure TestProcedure(Prc:TProcedureGenerique<UnType>);
  end;
signalerait un identificateur inconnu pour UnType.

Ici on pourra donc passer en paramètre n'importe quelle procédure de type TProcedureGenerique<A>.
Voici un exemple d'appel :

Procedure ProcedureGeneriqueInt(M:Integer);
begin
  Writeln(M);
end;

Procedure ProcedureGeneriqueString(M:String);
begin
  Writeln(M);
end;

..
  With TMaClasse.Create do
  begin
   TestProcedure<Integer>(ProcedureGeneriqueInt);

   //TestProcedure<Integer>(ProcedureGeneriqueString); //E2010 Types incompatibles : 'Integer' et 'string'
   //TestProcedure<String>(ProcedureGeneriqueInt); //E2010 Types incompatibles : 'string' et 'Integer'
   TestProcedure<String>(ProcedureGeneriqueString);
  end;
Notez que l'on doit préciser un argument de type pour ce type d'appel.

warning La construction suivante Procedure ProcedureGenerique<A>(M:A); provoque l'erreur suivante à la compilation :
E2530 Les paramètres de type ne sont pas autorisés sur la fonction ou la procédure globale.

Ce qui signifie que l'usage d'un type construit ouvert ne pourra donc se faire qu'au travers d'un membre d'une classe ou d'un record.
Voyons maintenant l'appel d'une méthode :

procedure TMaClasse.UneMethode<T>(X, Y: T);
begin
  Writeln(X.ToString,' , ',Y.ToString);
end;

procedure TMaClasse.TestMethode;
var
  P: TProcObjetGenerique<Boolean>; //Argument de type Boolean connu
begin
  UneMethode<String>('Hello', 'World'); //Argument de type String connu
  UneMethode('Hello', 'World');

  UneMethode<Integer>(10, 20); //Argument de type Integer connu
  UneMethode(10, 20);

  P:=UneMethode<Boolean>;
  P(False,True);
end;
Les différents arguments type de notre méthode générique étant précisés dans le corps de cette méthode de test, sa manipulation ne pose pas de problème particulier.


6-4-1. Le mot clé Default

Si avez chargé et exécuté le code du projet précédent vous aurez noté le déclenchement d'une exception NullReferenceException lors de l'appel de procédure TestProcedure<String> ainsi que l'usage du mot clé Default.

Regardons de plus près cette instruction.

procedure TMaClasse.TestProcedure<UnType>(Prc:TProcedureGenerique<UnType>);
var
  P: TProcedureGenerique<UnType>;
  Value: UnType;
begin
   //Le type est déterminé dans la signature, il est donc inconnu.
  Prc('Hello');
  P:=Prc;
  //Value:='Chaine'; //E2010 Types incompatibles : 'UnType' et 'string'
  Value:=Default(UnType);
    
  P(True);
  P(Default(UnType));
end;
Dans la signature et le corps de la procédure générique précédente, le type réel de UnType est inconnu au moment de l'écriture de ce code. On ne peut donc assigner une valeur quelconque à la variable Value.

Default renvoi la valeur par défaut du type, précisé par l'argument type utilisé. Pour le type Integer Default renverra 0.
Integer étant un type valeur, la valeur par défaut est dans ce cas connue mais si le type utilisé est un type référence, comme un TObjet ou une String, la valeur renvoyée sera égale à Nil.

warning On testera donc, avant toute manipulation, le contenu des variables de type référence en utilisant l'instruction Assigned.

 if assigned(TObject(Value)) //Le cast est obligatoire
   then writeln('Assigné.')
   else writeln('Non assigné.');
Le type étant inconnu lors de la compilation la manipulation de la varaible Value nécessite un transtypage en TObject.


6-5. Les méthodes

Projet : ..\Methodes1

Revenons sur la manipulation des méthodes génériques, dans la procédure TMaClasse.TestMethode vous aurez remarqué 2 types d'appel pour UneMethode<String> :

  UneMethode<String>('Hello', 'World');
  UneMethode('Hello', 'World');
Le second appel ne précise pas l'argument de type car on utilise l'inférence de type qui n'est autre que la déduction de l'argument de type par le compilateur d'après le type de donnée des arguments utilisés lors de l'appel. L'inférence reste possible tant qu'il n'y a pas d'ambigüité sur les types de donnée utilisés.

Comme nous l'avons vue, il est tout à fait possible de construire une classe et tous ses membres en utilisant des arguments de types.

  TGenericType<AnyType> = class
    FData: AnyType;
    function GetData: AnyType;

    procedure UneMethodeGenerique<AnyType>(Variable: AnyType);
    function UneFonctionGenerique<AnyType>: AnyType;

    procedure SetData(Value: AnyType);
    property Data: AnyType read GetData write SetData;
  end;
Cette construction génère l'avertissement suivant :

H2509 Identificateur 'AnyType' en conflit avec les paramètres type du type conteneur
car l'usage, dans la déclaration de la méthode UneMethodeGenerique, de l'argument de type AnyType, masque celui de la classe. Si seul le type de l'argument nommé Variable doit suivre celui indiqué lors de la construction de la classe générique vous pouvez ommettre le paramètre de type :

  TGenericType<AnyType> = class
    FData: AnyType;
    function GetData: AnyType;

    procedure UneMethodeGenerique(Variable: AnyType);
    function UneFonctionGenerique: AnyType;

    procedure SetData(Value: AnyType);
    property Data: AnyType read GetData write SetData;
  end;
Sinon il est recommandé d'utiliser un autre nom pour le paramètre de type :

    procedure UneMethodeGenerique<T>(Variable: T);
Notez que l'ajout des déclarations suivantes forcerait l'usage de la directive Overload sur la méthdoe UneMethodeGenerique :

    procedure UneMethodeGenerique(Variable: AnyType);Overlaod;
    procedure UneMethodeGenerique<R>(Variable: AnyType; Variable2: R);Overload;
    procedure UneMethodeGenerique<T,U>(Variable: U; Variable2: T);Overload;

6-6. Les délégués


6-7. Les interfaces

Projet : ..\Interfacegeneric

La définition d'interface permet l'usage de paramétre de type.

type
  IMonInterface<T>= interface
    procedure set_Valeur(const AValeur: T);
    function get_Valeur: T;
    property Valeur: T read get_Valeur write set_Valeur;
  end;

  IMonInterfaceDerivee<T>= interface(IMonInterface<T>)
   Procedure Multiplier(AMulplicateur:T);
  end;
A la compilation on verra que la procédure Multiplier ne peut être implémenté :

Procedure TClasseTest<T>.Multiplier(AMulplicateur:T);
begin
 //E2015 Opérateur non applicable à ce type d'opérande
 FCompteur:=FCompteur * AMulplicateur;  
end;
warning Comme on ne connait rien du type T certaines opérations ne seront pas possible avec les génériques, comme ici l'utilisation de l'opérateur multiplier.
info Le framework .NET 2.0 fournit un certain nombre d'interfaces génériques notamment dans l'espace de nom System.Collections.Generic.

6-8. Pour information

Les énumération ne peuvent pas être génériques. Enumerations in the Common Type System :
It is possible to declare a generic enumeration in Microsoft intermediate language (MSIL) assembly language, but a TypeLoadException occurs if an attempt is made to use the enumeration.

 Ensemble<T>=Set of T; //Erreur
Les assistants de classe ne sont pas autorisés avec les génériques, le code suivant ne compile pas :

 TTestNullableHelper<T:Record>=Class Helper for TTestNullable<T:Record>
 End;

 Erreur : E2508 Les paramètres de type ne sont pas autorisés sur ce type

7. Contraintes

Lors de la construction d'un générique on peut contraindre un argument de type à respecter certaines régles.
Par exemple dans le cas ou une classe générique utilise dans son code un itérateur IEnumérable on doit s'assurer que le type réel de l'argument de type rempli bien ce contrat.

Les mots clés CLASS, CONSTRUCTOR et RECORD permettront d'en spécifier une ou plusieurs. Notez que la construction suivante ne stipule aucune contrainte :

 TGenericType<T>
warning Les mots clés CLASS et RECORD sont des contraintes exclusives.

7-1. Sur une classe ancêtre

On contraint l'argument de type T à dériver d'une classe ancêtre particuliére, ici de la classe TComponent :

  TGenericType<T:TComponent>

7-2. Sur une ou plusieurs interfaces

On contraint l'argument de type à respecter le contrat d'une ou plusieurs interfaces générique ou non.
L'exemple suivant contraint l'argument de type T à implémenter l'interface générique IMonInterface<T>

  TGenericType<T:IMonInterface<T>>

7-3. Sur l'accés à un constructeur sans paramètre

On utilisera le mot clé Constructor pour contraindre un argument de type à posséder un constructeur sans paramètre et d'accés public.
On s'assure ainsi de pouvoir créer dans le corps des méthodes une instance du type passé en paramètre.
Voici un exemple :

7-4. Sur un type référence

On utilisera le mot clés CLASS pour contraindre l'usage d'un type référence, c'est à dire de n'importe quelle classe.

7-5. Sur un type enregistrement

On utilisera le mot clés RECORD pour contraindre l'usage d'un enregistrement (type valeur).

 TTestNullable<T:record>=Class
   procedure Inverser(Arg:System.Nullable<T>; Valeur:System.Nullable<T>);
 End;
info On peut bien sûr coupler ces contraintes en les séparant par une virgule et/ou les regrouper pour un ensemble d'argument de type :

  TGenericType<T:Constructor,TMonItérateur,IEnumérable>;
  
  TGenericType<T,U:IEnumérable>;

8. Héritage

Avec les génériques les possibilités d'héritage de classe se trouvent modifiées. Une classe de base peut hériter d'une classe générique et une classe générique peut hériter d'une autre classe générique.
Voyons quelques exemples :

 //Classe de base
  TClassDeBase=Class
   FData: integer;
  End;

   //Classe générique
  TGenerique<I>=Class
   FData: I;
  End;

   //Classe de base dérivée d'un type générique ouvert
  {TClassDeBaseDeriveDeGenerique=Class(TGenerique<I>) //E2003 Identificateur non déclaré : 'I'
   FData: integer;
  End;
  }
   //Classe de base dérivée d'un type générique fermé
  TClassDeBaseDeriveDeGenerique=Class(TGenerique<Integer>)
   FData: integer;
  End;

   //Classe générique dérivée d'une classe de base
  TGeneriqueDeriveeDeClass<I>=Class(TClassDeBase)
   FData: I;
  End;

   //Classe générique dérivée d'une classe générique
  TGeneriqueDeriveeDeGenerique<I,S>=Class(TGeneriqueDeriveeDeClass<I>)
   FData: I;
   Fitem: S;
  End;

   //Classe générique contrainte
  TGeneriqueContraint<I:constructor>=Class
   FData: I;
  End;

  //Classe générique dérivée d'une classe générique contrainte
   //E2513 Le paramètre type 'I' doit avoir un constructeur sans paramètre
   //TGeneriqueDeriveeDeGeneriqueContraint<I,S>=Class(TGeneriqueContraint<I>)
  TGeneriqueDeriveeDeGeneriqueContraint<I,S>=Class(TGeneriqueContraint<I:constructor>)
   FData: I;
   Fitem: S;
  End
Vous remarquerez que les classes de base (non génériques) peuvent hériter des classes de base construites fermées, mais pas des classes construites ouvertes car dans ce cas on ne peut pas "propager" l'argument de type requis pour instancier la classe de base générique.
Notez également, dans le dernier exemple, que les classes génériques contraintes utilisées dans un héritage doivent redéclarer leurs contraintes


8-1. Portée des paramètres de type

warning L'argument type T n'est visible que dans la déclaration et dans le corps des membres d'un type générique et pas dans les classes dérivées :

Type
   TParent<T> = class
     X: T;
   end;

   TEnfant<S> = class(TParent<S>)
     Y: T;  // Erreur! Identifieur inconnu "T" 
   end;

9. Surcharge de classe

Nous avons pu voir rapidement dans le chapitre Comment ça fonctionne ?, que les génériques autorise la surcharge de classe. Voyons les différents cas possible :

 TTest=Class
   FData: integer;
  End;
  //Surcharge générique
  TTest<I>=Class
   FData: I;
  End;

  {
  TTest<I:constructor>=Class //E2037 La déclaration de 'TTest<I>' diffère de la déclaration précédente
   FData: I;
  End;
  }

  TTest<I,S>=Class
   FData: I;
   Fitem: S;
  End;

  //Surcharge générique à partir d'une autre surcharge générique de la même classe
      TGeneric<A,B>=Class
       FData: A;
       Fitem: B;
      End;

  TGeneric<A>=Class(TGeneric<A,String>)
   FData: A;
  End;

  //Surcharge générique à partir d'une autre surcharge générique d'une autre classe
  TGenericTest<R,U>=Class(TGeneric<R>)
   FData: R;
  End;
Notez que la surcharge basée sur une contrainte est impossible.

info Extrait des spécification du C#:
Generic types can be "overloaded" on the number of type parameters; that is two type declarations within the same namespace or outer type declaration can use the same identifier as long as they have a different number of type parameters.

10. Variable de classe dans les génériques

Extrait de la documentation de Delphi 2007 :
La variable classe définie dans un type générique est instanciée dans chaque type instancié identifié par les paramètres de type.

Le code suivant montre que TFoo<Integer>.FCount et TFoo<String>.FCount ne sont instanciés qu'une seule fois, et que ce sont deux variables différentes.

{$APPTYPE CONSOLE}
type
  TFoo<T> = class
    class var FCount: Integer;
    constructor Create;
  end;

constructor TFoo T>.Create;
begin
  inherited Create;
  Inc(FCount);
end;

procedure Test;
var FI: TFoo<Integer>;
begin
  FI := TFoo<Integer>.Create;
  FI.Free;
end;

var
  FI: TFoo<Integer>;
  FS: TFoo<String>;

begin
  FI := TFoo<Integer>.Create;
  FI.Free;
  FS := TFoo<String>.Create;
  FS.Free;
  Test;
  WriteLn(TFoo<Integer>.FCount); // renvoie 2
  WriteLn(TFoo<String>.FCount);  // renvoie 1
end;

11. La classe System.Type et les génériques

Projet : ..\CreeGeneric

La classe System.Type du framework .NET 2.0 propose de nombreuse méthodes pour manipuler les types générique.
Notamment la méthode MakeGenericType :

Procedure TConteneur.Main;
var t,
    generique,
    construit : System.Type;
    typeArgs    : Array of System.Type;

begin
 try
  WriteLn('Crée un type construit à partir du type générique Dictionary.',  Environment.NewLine);
   {Crée un type objet représentant le type générique Dictionary,
   en omettant le type arguments (mais en gardant la virgule qui les sépare,
   ainsi le compilateur peut déduire le nombre de type paramètres).}
  generique:= typeof(Dictionary<,>);
  AfficheInformationDeType(generique);

   {Crée un tableau de type pour remplacer les paramètres de type Dictionary.
   le paramètre 'Key' est de type String, et le type à contenir dans Dictionary est TConteneur}
  typeArgs:= TArrayType.Create(typeof(string), typeof(Self));

  {Créez un type objet représentant le type générique construit.}
  construit:= generique.MakeGenericType(typeArgs);
  AfficheInformationDeType(construit);

  {Compare les types d'objet obtenus ci-dessus aux types objet obtenu
  en utilisant les methode typeof et GetGenericTypeDefinition.}
  WriteLn(Environment.NewLine,'Compare les types obtenus par les différentes méthodes :');

  //t:= typeof(System.Collections.Generic.Dictionary<String, Self>);   //E2003 Identificateur non déclaré : 'Dictionary'
  t:= typeof(TGenDictionary);
WriteLn('Les types construit sont-il égaux ? ', (t = construit));
WriteLn('Les type génériques sont-il égaux ? ',(t.GetGenericTypeDefinition = generique));
  except
    on E:Exception do
      Writeln(E.Classname, ': ', E.Message);
  end;
end;
Vous trouverez à ce sujet un exemple plus concis dans le projet CreeGeneric2.


12. Type Nullable

Le concept de nullable propose pour un type donné l'ajout d'un état supplémentaire à savoir l'état inconnu, rien ou zéro étant un état connu. La structure Nullable prend en charge l'utilisation unique d'un type valeur comme type nullable parce que les types référence sont nullables de conception, et ce à l'aide du mot clés nil.

Ce qui est confirmé par le code C# du code source CLI 2.0 :

 public struct Nullable<T> where T : struct

12-1. La déclaration

Le type System.Nullable étant un générique on déclare un type construit fermé :

var
    intNull : System.Nullable<integer>;
    dblNull : System.Nullable<Double>;
Si on souhaite déclarer une classe générique utilisant un argument de type nullable, on contraindra cet argument de type avec :record :

 TTestNullable<T:record>=Class
   procedure Inverser(Arg:System.Nullable<T>; Valeur:System.Nullable<T>);
 End;

12-2. L'assignation

L'assignation pour un type de base se fait simplement

procedure Assignation;
var  I:Integer;
     intNull:System.Nullable<integer>;
begin
  intnull:=52;
  //I:=intNull;  //E2010 Types incompatibles : 'Integer' et 'Nullable<System.Int32>'
  I:=Integer(intNull);
  I:=Convert.ToInt32(intNull);
Writeln('Gestion d''une valeur null');
  intnull:=Default(Nullable<integer>);
L'affectation de nil ne fonctionnant pas pour signifier l'affectation d'une valeur null, on utilisera la mot-clé Default :

  intnull:=Default(Nullable<integer>);

12-3. Diverses manipulations

Pour déterminer si la variable est à null ou pas, on utilisera soit directement la propriété HasValue :

procedure Test(Arg:System.Nullable<integer>);
begin
  if Arg.HasValue
   then writeln('La variable n''est pas à null')
   else writeln('La variable est à null')
end;
soit indirectement en accédant à la propriété Value si sa valeur est null l'exception InvalidOperationException est déclenchée :

procedure Test2(Arg:System.Nullable<integer>);
begin
 try
  Writeln('Valeur du type nullable =',Arg.Value);
  Except
    //l'accés à la propriété Value déclenche une exception si son contenu est Null
   On E:InvalidOperationException do
    Writeln('La variable est à null')
 end;
end;

13. Classe partielle

Les classes partielles ne sont pas possible sous Delphi .NET 2007.
voir http://groups.google.com/group/borland.public.delphi.non-technical/browse_thread/thread/a8db18a5c3501e71


14. Net 3.0 et WPF

Windows Communication Foundation (WCF) is the messaging framework of WinFX, the next generation Windows API. This session provides an overview of WCF and shows how you can take advantage of it in your applications.

http://conferences.codegear.com/article/33233 en Introduction to WCF Programming in Delphi
RAD Studio 2007 Developer Days replay, en WCF programming (.pdf 33 Mo) by Pawel Glowacki.

Comparing WPF on Windows Vista v. Windows XP http://blogs.msdn.com/tims/archive/2007/01/05/comparing-wpf-on-windows-vista-v-windows-xp.aspx fr Package redistribuable de Microsoft .NET Framework 3.0 ( nécessite un connexion internet pour continuer l'installation)
sdk 3.0 http://msdn2.microsoft.com/fr-fr/netframework/bb264589.aspx novembre 2006 http://www.microsoft.com/downloads/details.aspx?familyid=4377f86d-c913-4b5c-b87e-ef72e5b4e065&displaylang=en


15. Liens




Valid XHTML 1.1!Valid CSS!

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2007 Laurent Dardenne. 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 -