Programmation DotNet : Comment faire une énumération de type String ?

De ORSWiki
Aller à : navigation, rechercher

Le moyen le plus simple de faire une énumération de type String en DotNet est la suivante (exemple en VB .Net) :

   Public Module enumOuiNon
       Public Nul$ = "Nul"
       Public Oui$ = "Oui"
       Public Non$ = "Non"
   End Module
   Dim maVar$ = enumOuiNon.Oui
   Debug.WriteLine(maVar)

Ou bien alors, sans oublier le Const cette fois :

  Public Class enumOuiNon
      Public Const Nul$ = "Nul"
      Public Const Oui$ = "Oui"
      Public Const Non$ = "Non"
  End Class

L'inconvénient, c'est que vous pouvez alors mettre n'importe quoi dans votre variable, par exemple :

   Dim maVar$ = "Toto va à la plage"

Tout simplement parce que votre énumération a un typage faible : seul le type String est vérifié à la compilation, et non les différentes valeurs permises de l'énumération.

Pour pouvoir restreindre à la compilation les valeurs permises de l'énumération à votre variable, il faut lui appliquer un typage fort, mais cela requiert d'écrire un peu plus de code :

   Public Structure TTypeOuiNon : Implements IComparable
        <summary>
        Le type n'est pas defini
        </summary>
       Public Shared ReadOnly Property Nul() As TTypeOuiNon
       Get
           Return New TTypeOuiNon(enumOuiNon.Nul)
       End Get
       End Property
        <summary>
        Le type vaut Oui
        </summary>
       Public Shared ReadOnly Property Oui() As TTypeOuiNon
       Get
           Return New TTypeOuiNon(enumOuiNon.Oui)
       End Get
       End Property
        <summary>
        Le type vaut Non
        </summary>
       Public Shared ReadOnly Property Non() As TTypeOuiNon
       Get
           Return New TTypeOuiNon(enumOuiNon.Non)
       End Get
       End Property
       Private m_sValeur$
       Public Sub New(ByVal sValeur$)
           m_sValeur = sValeur
       End Sub
       Public Overrides Function ToString$()
           Return m_sValeur
       End Function
       Public Overloads Overrides Function Equals(ByVal obj As Object) As Boolean
           Dim sValeur$ = DirectCast(obj, TTypeOuiNon).ToString()
           Dim bEgal As Boolean = (sValeur = m_sValeur)
           Return bEgal
       End Function
       Public Overloads Function CompareTo(ByVal autre As Object) As Integer _
           Implements IComparable.CompareTo
           ' If other is not a valid object reference, this instance is greater.
           If IsNothing(autre) Then Return 1
           Dim sValeur$ = autre.ToString
           Return m_sValeur.CompareTo(sValeur)
       End Function
       ' Surcharges d'opérateurs
       Public Shared Operator =(ByVal typeG As TTypeOuiNon, ByVal typeD As TTypeOuiNon) As Boolean
           Return typeG.Equals(typeD)
       End Operator
       Public Shared Operator <>(ByVal typeG As TTypeOuiNon, ByVal typeD As TTypeOuiNon) As Boolean
           Return Not typeG.Equals(typeD)
       End Operator
       Public Shared Operator &(ByVal typeG$, ByVal typeD As TTypeOuiNon) As String
           Return typeG & typeD.ToString
       End Operator
       Public Shared Operator &(ByVal typeG As TTypeOuiNon, ByVal typeD As TTypeOuiNon) As String
           Return typeG.ToString & typeD.ToString
       End Operator
       Public Function bEstValide() As Boolean
           Select Case m_sValeur
           Case enumOuiNon.Nul, enumOuiNon.Oui, enumOuiNon.Non : Return True
           Case Else : Return False
           End Select
       End Function
   End Structure

Vous pouvez maintenant écrire ceci :

   Dim maVarOui As TTypeOuiNon = TTypeOuiNon.Oui
   Dim maVarNon As TTypeOuiNon = TTypeOuiNon.Non
   Dim maVarNul As TTypeOuiNon = TTypeOuiNon.Nul
   'Dim maVarAutre As TTypeOuiNon = "Toto" ' Ne compile pas
   Dim maVarAutre As TTypeOuiNon = New TTypeOuiNon("Toto")
   Dim maVarOui2 = TTypeOuiNon.Oui
   Dim lst As New List(Of TTypeOuiNon) From {maVarOui, maVarNon, maVarNul, maVarAutre, maVarOui2}
   Debug.WriteLine("maVarOui : " & maVarOui)
   Debug.WriteLine("maVarNon : " & maVarNon)
   Debug.WriteLine("maVarNul : " & maVarNul)
   Debug.WriteLine("maVarAutre : " & maVarAutre & ", valide : " & maVarAutre.bEstValide)
   Debug.WriteLine("maVarOui = maVarNon : " & (maVarOui = maVarNon))
   Debug.WriteLine("maVarOui <> maVarNon : " & (maVarOui <> maVarNon))
   Debug.WriteLine("maVarOui = maVarOui2 : " & (maVarOui = maVarOui2))
   Debug.WriteLine("maVarOui & maVarOui2 : " & (maVarOui & maVarOui2))
   Debug.WriteLine("Avant le tri :")
   For Each ouiNon In lst
       Debug.WriteLine(ouiNon)
   Next
   lst.Sort()
   Debug.WriteLine("Après le tri :")
   For Each ouiNon In lst
       Debug.WriteLine(ouiNon)
   Next

Résultats :

   maVarOui : Oui
   maVarNon : Non
   maVarNul : Nul
   maVarAutre : Toto, valide : False
   maVarOui = maVarNon : False
   maVarOui <> maVarNon : True
   maVarOui = maVarOui2 : True
   maVarOui & maVarOui2 : OuiOui
   Avant le tri :
   Oui
   Non
   Nul
   Toto
   Oui
   Après le tri :
   Non
   Nul
   Oui
   Oui
   Toto


On pourrait aussi simplifier le code en remplaçant la propriété (Property) directement par :

   Public Shared ReadOnly Nul As New TTypeOuiNon(enumOuiNon.Nul)

mais dans ce cas les commentaires (summary), qui permettent l'affichage d'information lorsque l'on promène la souris sur la variable, ne fonctionnent plus (sous Visual Studio 2010, mais cela a été corrigé dans la version 2013).


Ah au fait, on aurait pu aussi tout simplement utiliser un Enum entier classique avec ToString :

   Public Enum enumOuiNonEntier
       Nul
       Oui
       Non
       Non_Défini
   End Enum
   Dim maVarOui3$ = enumOuiNonEntier.Oui.ToString
   Dim maVarNon3$ = enumOuiNonEntier.Non.ToString
   Dim maVarNul3$ = enumOuiNonEntier.Nul.ToString
   Dim maVarNon_Défini$ = enumOuiNonEntier.Non_Défini.ToString
   Debug.WriteLine("maVarOui3 : " & maVarOui3)
   Debug.WriteLine("maVarNon3 : " & maVarNon3)
   Debug.WriteLine("maVarNul3 : " & maVarNul3)
   Debug.WriteLine("maVarNon_Défini : " & maVarNon_Défini)

Résultats :

   maVarOui3 : Oui
   maVarNon3 : Non
   maVarNul3 : Nul
   maVarNon_Défini : Non_Défini

Mais l'inconvénient est qu'on ne peut alors pas utiliser d'espace dans le nom de l'énumération. On pourrait cependant utiliser un attribut :

   Public Enum enumOuiNonEntier
       Nul
       Oui
       Non
       <System.ComponentModel.Description("Non Défini")> _
       Non_Défini
   End Enum

Mais alors la récupération de l'attribut obligerait à écrire du code, que l'on ne pourrait pas insérer dans l'énumération elle-même, ce qui obligerait du coup à refaire une classe, comme indiquée dans la première solution :

   Dim type0 = GetType(enumOuiNonEntier)
   Dim memInfo = type0.GetMember(enumOuiNonEntier.Non_Défini.ToString())
   Dim attributes = memInfo(0).GetCustomAttributes(GetType(System.ComponentModel.DescriptionAttribute), False)
   Dim description = DirectCast(attributes(0), System.ComponentModel.DescriptionAttribute).Description
   Debug.WriteLine("maVarNon_Défini : " & description)

Résultat :

   maVarNon_Défini : Non Défini

Voici tout de même la fonction qui pourrait être utilisée :

   Imports System.ComponentModel
   Public Function lireDescriptionEnum$(ByVal monEnum As [Enum])
       Dim fi As Reflection.FieldInfo = monEnum.GetType().GetField(monEnum.ToString())
       Dim aAttr() As DescriptionAttribute = DirectCast( _
           fi.GetCustomAttributes(GetType(DescriptionAttribute), False), DescriptionAttribute())
       If aAttr.Length > 0 Then
           Return aAttr(0).Description
       Else
           Return monEnum.ToString()
       End If
   End Function
   Debug.WriteLine(lireDescriptionEnum(enumOuiNonEntier.Non_Défini))
   Debug.WriteLine(lireDescriptionEnum(enumOuiNonEntier.Non))


En conclusion, il est intéressant d'utiliser la programmation générique, car chaque classe d'énumération reprend toujours la même structure, voici comment on peut faire :


   Option Infer On
   Imports System.Reflection ' FieldInfo
   Public MustInherit Class EnumGenerique(Of T As {EnumGenerique(Of T), New}) : Implements IComparable
       Protected Shared ReadOnly m_afiChamps As FieldInfo() = Nothing
       Protected m_iValeur%
       Protected m_sNom$
       Shared Sub New()
           m_afiChamps = GetType(T).GetFields()
       End Sub
       Protected Sub New(iVal As Integer)
           Me.m_iValeur = iVal
       End Sub
       Protected Sub New(iVal As Integer, sNom$)
           Me.New(iVal)
           Me.m_sNom = sNom
       End Sub
       Protected Sub New()
       End Sub
       Public Shared Operator =(t1 As EnumGenerique(Of T), t2 As EnumGenerique(Of T)) As Boolean
           If Object.ReferenceEquals(t1, t2) Then Return True
           If (DirectCast(t1, Object) Is Nothing) OrElse _
              (DirectCast(t2, Object) Is Nothing) Then Return False
           Return t1.m_iValeur = t2.m_iValeur
       End Operator
       Public Shared Operator <>(t1 As EnumGenerique(Of T), t2 As EnumGenerique(Of T)) As Boolean
           Return Not (t1 Is t2)
       End Operator
       Public Overrides Function ToString() As String
           If m_sNom IsNot Nothing Then
               Return m_sNom
           Else
               Dim property0 = m_afiChamps.FirstOrDefault(Function(f) DirectCast(f.GetValue(Me), T) = Me)
               If IsNothing(property0) Then Return ""
               Return property0.Name
           End If
       End Function
       Public Overrides Function Equals(obj As Object) As Boolean
           If TypeOf obj Is EnumGenerique(Of T) Then
               Return Me Is DirectCast(obj, EnumGenerique(Of T))
           Else
               Return False
           End If
       End Function
       Public Overloads Function CompareTo(ByVal autre As Object) As Integer _
              Implements IComparable.CompareTo
           ' Si l'autre élément n'est pas valide, cette instance est supérieur
           If IsNothing(autre) Then Return 1
           ' Tri texte (tri sur l'intitulé de l'enumération)
           Dim sValeur$ = autre.ToString
           Return Me.ToString.CompareTo(sValeur)
           ' Tri numérique (tri sur la valeur de l'énumération)
           'Dim iValeur% = DirectCast(autre, EnumGenerique(Of T)).m_iValeur
           'Return m_iValeur.CompareTo(iValeur)
       End Function
       Public Overrides Function GetHashCode() As Integer
           Return Me.m_iValeur.GetHashCode()
       End Function
   End Class
   Public Class Booleen4Etats : Inherits EnumGenerique(Of Booleen4Etats)
       Public Shared ReadOnly Nul As Booleen4Etats = 0
       Public Shared ReadOnly Oui As Booleen4Etats = 1
       Public Shared ReadOnly Non As Booleen4Etats = 2
       Public Shared ReadOnly PeutEtre As Booleen4Etats = 4
       Public Sub New()
           MyBase.New()
       End Sub
       Private Sub New(iVal%)
           MyBase.New(iVal)
       End Sub
       ' Opérateur de conversion implicite de Integer vers Booleen4Etats
       ' http://msdn.microsoft.com/fr-fr/library/z5z9kes2.aspx : C#
       ' http://msdn.microsoft.com/fr-fr/library/yf7b9sy7.aspx : VB
       Public Shared Widening Operator CType(iVal%) As Booleen4Etats
           Return New Booleen4Etats(iVal)
       End Operator
       ' Opérateur de conversion implicite de Booleen4Etats vers Integer
       Public Shared Widening Operator CType(val As Booleen4Etats) As Integer
           Return val.m_iValeur
       End Operator
   End Class

Pour des questions de performance et par souci d'utiliser les bonnes pratiques de programmation, mon conseil est tout de même plutôt d'utiliser cette librairie directement en tant que package nuget : https://github.com/TylerBrinkley/Enums.NET