I. Public concerné▲
Testé sous le framework .NET 1.1 et Delphi 2005.
Version 1.0
I-A. Les sources▲
II. 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 a 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 leur 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).
III. 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 chaines 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 chaines 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
;
IV. 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
;
V. 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 chaine 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.
VI. 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));
À 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'.