Accueil
Rechercher:
sur developpez.com sur les forums
Forums | Tutoriels | F.A.Q's | Participez | Hébergement | Contacts
Club Emploi Blogs   TV   Dév. Web PHP XML Python Autres 2D-3D-Jeux Sécurité Windows Linux PC Mac
Accueil Conception Java DotNET Visual Basic  C  C++ Delphi MS-Office SQL & SGBD Oracle  4D  Business Intelligence
FORUMS DELPHI F.A.Q DELPHI TUTORIELS DELPHI LIVRES COMPOSANTS SOURCES DEFI TELECHARGEZ DELPHI TV

Les énumérations sous Delphi .NET

Date de publication : 01/07/2005 , Date de mise a jour : 01/07/2005

Par Laurent Dardenne (Contributions)
 

Les énumérations du framework .NET me semblaient être un type de donnée différent mais il n'en est rien, elles se manipulent de la même manière qu'une énumération déclarée sous Delphi .NET.

Vous trouverez dans ce tutoriel les manipulations de base sur les énumérations sous Delphi .NET ainsi qu'une approche de la technique d'injection de code CIL au travers de la classe EnumBuilder.

Je tiens à remercier Olivier Lance (Bestiol) pour ses corrections orthographiques et relectures attentives.


1. Public concerné
1-1. Les sources
2. Les bases
3. Une nouvelle approche
4. Manipulation des énumérations du framework
5. Du nouveau : les champs de bits
6. Construction dynamique d'une énumération avec EnumBuilder


1. Public concerné

Testé sous le framework .NET 1.1 et Delphi 2005.
Version 1.0



1-1. Les sources

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


2. Les bases

Projet : ..\Enum\BasesEnumeration

Sous Delphi une énumération est un type simple de type scalaire :

Les valeurs possibles d'un type scalaire forment un ensemble ordonné; à chaque valeur est associé un rang 
qui est une valeur entière.
La première valeur à le rang zéro et l'ordre est celui défini lors de la déclaration.
Les identificateurs d'une énumération sont considérés comme des constantes du type énuméré.
Dans l'exemple suivant l'énumération Couleurs débute avec la valeur zéro, c'est-à-dire la constante nommée Bleu :

Type Couleurs = (Bleu,Blanc,Noir,Rouge,Vert);
Voici les instructions autorisées sur une énumération : Ord, Succ, Pred, Low, High

Un exemple de leurs utilisation :

Type // Enumération débutant à l'indice zéro Couleurs = (Bleu,Blanc,Noir,Rouge,Vert); Function GetCouleur(EtatCourant : Couleurs):String; // Affiche une string correspondant au contenu de la variable énumération EtatCourant begin case EtatCourant of Bleu : Result:='Bleu'; Blanc : Result:='Blanc'; Noir : Result:='Noir'; Rouge : Result:='Rouge'; Vert : Result:='Vert'; end; end; Var uneCouleur : Couleurs; I: Byte; begin Write('Affectation : '); uneCouleur:=Bleu; Writeln(GetCouleur(uneCouleur)); Write('Valeur suivante : '); uneCouleur:=Succ(uneCouleur); Writeln(GetCouleur(uneCouleur)); Write('Valeur précédente :'); uneCouleur:=Pred(uneCouleur); Writeln(GetCouleur(uneCouleur)); Writeln; Write('Affectation : '); uneCouleur:=Bleu; Writeln(GetCouleur(uneCouleur)); Write('Incrémente : '); Inc(UneCouleur); Writeln(GetCouleur(uneCouleur)); Write('Décrémente : '); Dec(UneCouleur); Writeln(GetCouleur(uneCouleur)); Write('Obtient la valeur minimum : '); Writeln(GetCouleur(Low(Couleurs))); Write('Obtient la valeur maximum : '); Writeln(GetCouleur(High(Couleurs))); Writeln; Write('Accès par indice (3) : '); Writeln(GetCouleur(Couleurs(3))); // Débute à zéro Write('Liste ordonnée des valeurs : '); For I:=byte(Low(Couleurs)) to Byte(High(Couleurs)) do begin Write(GetCouleur(Couleurs(I))); if I<>Byte(High(Couleurs)) then Write(', '); end; Readln; end.
Projet : ..\Enum\BasesEnumeration2

La fonction GetCouleur n'est plus nécessaire si on utilise les fonctionnalités objet du framework :

Write('Affectation : '); uneCouleur:=Bleu; // Transtypage obligatoire, Boxing Writeln(TObject(uneCouleur));
On remplace tous les appels de la fonction GetCouleur(uneCouleur) par TObject(uneCouleur).

Vous pouvez aussi consulter ce tutoriel au sujet des énumérations (Delphi Win32).


3. Une nouvelle approche

Dans le premier exemple le type Couleurs est une énumération mappée sur System.Enum de type Byte. On peut déclarer une énumération débutant avec un indice supérieur à zéro :

type Conteneur= (Carton= 52, Valise= 137); // La valeur détermine le type sous jacent.
Ici l'énumération Conteneur débute à l'indice 52 et se termine à 137, son type de base est donc Byte. Si on modifie la dernière constante en lui affectant 257 son type de base sous-jacent est modifié en Word. Il ne semble pas possible comme en C# de préciser le type de base de l'énumération en dehors de cette construction.

// Définit une énumération de type LONG enum Range :long {Max = 2147483648L, Min = 255L};
L'appel de la méthode Enum.GetUnderlyingType permet de retrouver le type sous-jacent :

var UneBoite : Conteneur; begin // Affiche le type sous-jacent d'une variable énumération Writeln('Type sous-jacent de l''énumération UneBoite : '); //Appel sur une variable énumération Writeln(Enum.GetUnderlyingType(TObject(UneBoite).GetType)); // ou directement Writeln(Enum.GetUnderlyingType(TypeOf(UneBoite))); //Appel sur un type énumération Writeln(Enum.GetUnderlyingType(Typeof(Conteneur)));
La recherche d'une constante d'une énumération se fait via la méthode Enum.isDefined :

//Teste si la valeur spécifiée existe dans une énumération. If System.Enum.isDefined(TypeOf(Conteneur),TObject(1)) then Writeln('la valeur 1 existe dans l''énumération Conteneur') else Writeln('la valeur 1 n''existe pas dans l''énumération Conteneur'); // La recherche est 'case sensitive' If System.Enum.isDefined(TypeOf(Conteneur),'valise') then Writeln('la valeur valise existe dans l''énumération Conteneur') else Writeln('la valeur valise n''existe pas dans l''énumération Conteneur');
Attention cette méthode de recherche est case-sensitive.
L'usage d'un itérateur est possible sur une énumération. Dans l'exemple suivant on manipule des chaînes des caractères représentant les constantes nommées :

var Chaine : String; begin // Affiche les valeurs possibles de l'énumération // Récupère un tableau de chaînes de caractères for Chaine in Enum.GetNames(typeof(Couleurs)) do begin WriteLn(Chaine); end;
Pour récupérer et manipuler chaque entrée de l'énumération on utilise la méthode Enum.GetValues :

var UneBoite : Conteneur; begin // Affiche les valeurs possibles de l'énumération // Récupére un tableau d'objets for UneBoite in Enum.GetValues(typeof(Couleurs)) do begin WriteLn(TObject(UneBoite)); end;

4. Manipulation des énumérations du framework

Projet : ..\Enum\BasesEnumeration3


Les énumérations du framework peuvent être manipulées comme celles déclarées sous Delphi :

Type //Alias, facilite l'écriture TEnvFolder = Environment.SpecialFolder; Var Chaine : String; Folder : Environment.SpecialFolder; begin //Environment.SpecialFolder Folder:=TEnvFolder.ApplicationData; Writeln(TObject(Folder)); // Accès direct Writeln(TObject(TEnvFolder.ApplicationData)); Writeln(Ord(TEnvFolder.ApplicationData)); Writeln(TObject(Succ(TEnvFolder.ApplicationData))); Folder:=Succ(Succ(TEnvFolder.ApplicationData)); Writeln(TObject(Folder)); Folder:=Pred(Folder); Writeln(TObject(Folder)); Writeln(TObject(Low(Environment.SpecialFolder))); Writeln(TObject(High(Environment.SpecialFolder))); Writeln(TObject(Environment.SpecialFolder(5))); // 5=Personnal Writeln; // Affiche les valeurs possibles de l'énumération for Chaine in Enum.GetNames(typeof(Environment.SpecialFolder)) do begin Write(Chaine+' , '); end; // Test de valeur if Folder=TEnvFolder.ApplicationData then WriteLn; readln;
Il est possible de tester si une valeur existe dans une énumération avec le code suivant :

if ((flagAttr and FileAttributes.Archive) = FileAttributes.Archive) then Console.WriteLine(FileAttributes.Archive);
Pour tester toutes les valeurs d'une énumération vous pouvez aussi utiliser un itérateur :

procedure GetFileAttributes(NomFichier : String) ; // Test : GetFileAttributes('C:\IO.SYS'); var flagAttr : FileAttributes; unAttr : System.IO.FileAttributes; begin With System.IO.File do begin flagAttr := GetAttributes(NomFichier); for UnAttr in Enum.GetValues(TypeOf(System.IO.FileAttributes)) do if ( (flagAttr and unAttr) = UnAttr) then Console.WriteLine(UnAttr); end; end;

5. Du nouveau : les champs de bits

Projet : ..\Enum\GestionEnum

Il est possible de déclarer une énumération optimisée par l'utilisation de l'attribut [FlagsAttribute], dans ce cas elle est traitée comme un champ de bits, c'est-à-dire un ensemble d'indicateurs :

[FlagsAttribute] DroitFichier = (Modification= 1, Lecture = 2, Ecriture= 4, Effacement= 8, All=15);
Les champs de bits sont généralement utilisés pour des listes d'éléments qui peuvent être combinées alors que les constantes énumérées sont généralement utilisées pour des listes d'éléments qui s'excluent mutuellement.

L'affectation se fait soit avec l'opérateur OR (son usage n'est pas possible avec des constantes énumérées) :

Var Droits : DroitFichier; begin // Champs de Bit Droits := Lecture; Writeln(TObject(Droits)); Droits := Droits or Ecriture; Writeln(TObject(Droits)); //Affichage au format String Writeln(TObject(Droits)); Writeln(Format('Resultat=%s',TObject(Droits))); //Affichage au format numérique Writeln(Convert.ToInt32(Droits)); Writeln(Format('Resultat=%d',TObject(Droits))); Writeln;
soit avec une des méthodes surchargées de Enum.Parse :

//Affectation via une chaîne de caractères NouveauDroit:='Ecriture,Effacement'; Droits:=DroitFichier(System.Enum.Parse(TypeOf(DroitFichier),NouveauDroit)); // OU Resultat:=(System.Enum.Parse(TypeOf(DroitFichier),'Ecriture,Effacement')) as DroitFichier; Writeln(TObject(Droits)); //Affectation via une valeur numérique DroitModifie:= DroitFichier(System.Enum.ToObject(TypeOf(DroitFichier),4)); WriteLn(TObject(DroitModifie)); //Ajout d'une valeur à une énumération existante NouveauDroit:=','+'Lecture'; Droits:=DroitFichier(System.Enum.Parse(TypeOf(DroitFichier),Tobject(Droits).ToString+NouveauDroit)); Writeln(TObject(Droits));
La méthode Parse permet aussi d'ajouter une valeur à une énumération existante :

NouveauDroit:=','+'Lecture'; Droits:=DroitFichier(System.Enum.Parse(TypeOf(DroitFichier),TObject(Droits).ToString+NouveauDroit)); Writeln(TObject(Droits)); //Ajoute une valeur String à une énumération existante NouveauDroit:=','+'Lecture'; Droits:=DroitFichier(System.Enum.Parse(TypeOf(DroitFichier),TObject(Droits).ToString+NouveauDroit)); Writeln(TObject(Droits)); //'Retire' une valeur Numérique de l'énumération I:=Convert.ToInt32(Droits) And not Convert.ToInt32(Ecriture); Writeln(Format('Resultat int=%d',TObject(I)));
soit avec les fonctions de l'unité TypInfo.pas qui appellent en interne la méthode Enum.Parse :

// Utilisation des fonctions de l'unité TypInfo.pas Chaine:=GetEnumName(TypeInfo(DroitFichier),4); // 4=Ecriture Writeln('Recherche par valeur : ',Chaine); I:=GetEnumValue(TypeInfo(DroitFichier),'Ecriture'); Writeln('Recherche par nom de constante : ',I.ToString); Droits:=DroitFichier(GetEnumObject(TypeInfo(DroitFichier),'Ecriture')); Writeln('Utilisation de GetEnumObject ',TObject(Droits));
Si on affecte une valeur représentant le résultat de l'addition de tous les champs, l'affichage n'est plus détaillé. Dans ce cas la méthode ToString appel en interne la méthode Format qui affichera la constante appropriée :

Droits := Modification or Lecture or Ecriture or Effacement; // l'ensemble des valeurs est égal à All Writeln(TObject(Droits)); Writeln(Format('Resultat =%s',TObject(Droits)));
dans ce cas l'affichage sera :

 Resultat = All
Pour retrouver les différentes valeurs associées à une énumération vous pouvez utiliser le code suivant :

for I:=0 to 512 do begin if TObject(Environment.SpecialFolder(I)).ToString<>I.ToString then Console.Writeline('{0} Value= {1}',[TObject(Environment.SpecialFolder(I)),I]); end;
L'appel de TObject(Environment.SpecialFolder(3)) affichera 3 étant donné que cette valeur n'est pas associée à une constante nommée dans l'énumération Environment.SpecialFolder.
Voir :
 Le code source de System.Enum, attention il s'agit d'une préversion du framework .NET datant de 2000.
 La classe Enum.


6. Construction dynamique d'une énumération avec EnumBuilder

Projet : ..\Enum\ExempleEnumBuilder

Le Framework .NET permet la génération de code à la volée appelé aussi injection de code CIL, il propose pour ce faire un ensemble de classes débutant par 'Builder'. Cette technique n'est pas détaillée dans ce tutoriel mais vous pouvez consulter ce lien sur le sujet ou mieux encore parcourir ce chapitre intitulé "La génération de code" issue du livre  .NET de Dick Lantim.

Extrait de ce chapitre :

Pour maîtriser la génération de code à la volée, il convient de bien connaître la structure d’un assemblage.
Pour rappel, un assemblage est constitué d’un ou de plusieurs modules (c’est-à-dire un assemblage de 
NetModules). Chaque module dispose d’une ou de plusieurs classes. Chaque classe dispose de données membres 
et de méthodes.
Cet exemple est simplifié. On peut par exemple spécifier une version, une langue et un couple de clés publique/privée pour protéger le code IL généré.
Il est donc possible, au travers de la classe EnumBuilder, de construire dynamiquement une énumération à l'intérieur d'un assembly lui même créé dynamiquement.

La première étape consiste à créer un assemblage dans le domaine de l'application, domaine que l'on récupère par l'appel à la méthode Thread.GetDomain ou AppDomain.CurrentDomain :

const cstAssemblage='AssemblyDynamique'; // Nom de l'assembly AssemblyAcces = AssemblyBuilderAccess.RunAndSave; // Type d'accès de l'assembly var NomAssembly : AssemblyName; MonDomain : AppDomain begin MonDomain:=Thread.GetDomain; // Affecte un nom pour l'assembly. NomAssembly:=AssemblyName.Create; NomAssembly.Name:=cstAssemblage;
La création du nouvel assemblage s’effectue par l'appel de la méthode DefineDynamicAssembly à partir de l'instance de domaine précédemment récupérée, le mode d'accés autorisera ou non certaines opérations :

Var ConstructeurAssembly : AssemblyBuilder; // Définit et représente un assembly dynamique begin ... ConstructeurAssembly:=MonDomain.DefineDynamicAssembly(NomAssembly,access);
Ensuite il nous faut créer un module par l'appel de la méthode DefineDynamicModule à partir de l'instance de l'assembly précédemment créé :

Var ConstructeurModule : ModuleBuilder; // Définit et représente un module. Module Dynamique begin ... // Créer un module dynamique persistant case Access of // si l'assembly est persistant nécessite un nom de module et un nom de fichier. AssemblyBuilderAccess.Save, AssemblyBuilderAccess.RunAndSave : ConstructeurModule:= ConstructeurAssembly.DefineDynamicModule('ModuleDynamique','ModuleDynamique.mod'); // si l'assembly n'est pas persistant nécessite uniquement un nom de module. AssemblyBuilderAccess.Run : ConstructeurModule:= ConstructeurAssembly.DefineDynamicModule('ModuleDynamique'); end;
Une fois l'assembly et le module créés, il nous reste à définir la classe souhaitée, ici une énumération, en appelant DefineEnum à partir de l'instance du module précédemment créé :

Var ContructeurEnum : EnumBuilder; // Décrit et représente un type d'énumération begin ... ContructeurEnum:= ConstructeurModule.DefineEnum('Enumeration.EnumerationDynamique',TypeAttributes.Public, typeof(Integer));
DefineEnum définit un type d'énumération qui est un type valeur avec un champ non statique unique appelé value__ du type spécifié.
Puis on passe à la définition des membres de la classe, ici il s'agit des constantes nommées, en appelant la méthode DefineLiteral qui définit un champ statique :

var myFieldBuilder1, myFieldBuilder2 : FieldBuilder; valeur1, valeur2 : Integer; Begin ... valeur1:=1; valeur2:=2; // Le double transtypage est nécessaire //Définit le champ statique nommé d'un type d'énumération à l'aide de la valeur de constante spécifiée. myFieldBuilder1:= ContructeurEnum.DefineLiteral('Premier', TObject(Integer(1))); myFieldBuilder2:= ContructeurEnum.DefineLiteral('Deuxieme', TObject(Integer(2))); // Il est possible d'utiliser des variables : // myFieldBuilder1:= ContructeurEnum.DefineLiteral('Premier', TObject(valeur1)); // myFieldBuilder2:= ContructeurEnum.DefineLiteral('Deuxieme', TObject(valeur2));
Le champ défini sera doté des attributs de champ Public, Static et Literal.

Le simple transtypage suivant :

myFieldBuilder1:= ContructeurEnum.DefineLiteral('Premier', TObject(1)); myFieldBuilder2:= ContructeurEnum.DefineLiteral('Deuxieme', TObject(2));
provoque l'exception ArgumentException :

"La constante ne correspond pas au type défini."
Et enfin on termine l'opération par la création de la classe et en récupérant son type.

var TypeDynEnum : System.Type; begin ... TypeDynEnum:=ContructeurEnum.CreateType; end;
L'enregistrement sur disque se fait de la façon suivante :

If Not (AssemblyAcces=AssemblyBuilderAccess.Run) // L'assembly doit pouvoir être enregistré (persistant) then ConstructeurAssembly.Save(cstAssemblage+'.dll');
L'objectif étant de manipuler cette énumération générée, on crée une instance de la classe par l'appel de la méthode Activator.CreateInstance :

var I : Integer; MonEnum, UneEnum : System.Enum; begin ... // Manipule l'enumération créée If Not (AssemblyAcces=AssemblyBuilderAccess.Save) // L'assembly doit pouvoir être exécuté then begin MonEnum:=System.Enum(Activator.CreateInstance(TypeDynEnum)); Writeln('Contenu de l''énumération crée :'); for UneEnum in Enum.GetValues(typeof(MonEnum)) do begin WriteLn(TObject(UneEnum)); end; // Ou pour ce contexte, directement { for UneEnum in Enum.GetValues(TypeDynEnum) do } end else Writeln('L''énumération dynamique ne peut être instanciée avec le mode d''accès : ',TObject(AssemblyAcces));
A partir du moment où "Les identificateurs d'une énumération sont considérés comme des constantes du type énuméré", les instructions Ord, Succ, Pred, Low, High ne pourront être utilisées sur des 'constantes' d'une énumération dynamique pour la simple raison qu'elles sont inconnues du compilateur.
Je vous invite à consulter les autres classes 'Builder', sachez que certaines opérations s'avèreront un peu plus complexes à mettre en œuvre notamment la création du code d'une méthode de classe ...


Voir :
La classe EnumBuilder.
System.Reflection.Emit, espace de nom contenant les classes 'builder'.



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 oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2005 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'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts. Cette page est déposée à la SACD.

Responsables bénévoles de la rubrique Delphi : Nono40 et Pedro - Contacter par EMail :
Vos questions techniques : forum d'entraide Delphi - Publiez vos articles, tutoriels et cours
et rejoignez-nous dans l'équipe de rédaction du club d'entraide des développeurs francophones
Nous contacter - Copyright © 2000-2008 www.developpez.com - Legal informations.