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

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage Delphi Discussion :

Simplification de la Conversion entre les Types Énumérés et les Strings en Delphi


Sujet :

Langage Delphi

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre éclairé

    Homme Profil pro
    Mathématicien
    Inscrit en
    Juin 2016
    Messages
    29
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Marne (Champagne Ardenne)

    Informations professionnelles :
    Activité : Mathématicien

    Informations forums :
    Inscription : Juin 2016
    Messages : 29
    Par défaut Simplification de la Conversion entre les Types Énumérés et les Strings en Delphi
    Je travaille actuellement avec des types énumérés en Delphi et je me retrouve souvent à écrire des "record helpers" pour faciliter la conversion entre ces types énumérés et les chaînes de caractères (string). Par exemple, pour un type énuméré représentant différents modèles de conception, je crée un helper pour ajouter des méthodes comme ToString, FromString et ToArray.

    Voici un exemple de code pour illustrer mon propos :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    type
      TMyEnumtype = (abstract_factory, builder, factory_method, prototype, singleton);
      TMyEnumtypeHelper = record helper for TMyEnumtype
        function ToString: string;
        function FromString(Value: string): TMyEnumtype;
        class function ToArray(CaseRespect: Boolean = False): TArray<string>; static;
      end;
    Ma question est la suivante : Existe-t-il une façon plus native ou générique en Delphi pour obtenir la conversion entre un type énuméré et une chaîne de caractères, sans avoir à écrire un "record helper" spécifique pour chaque type énuméré ? Autrement dit, y a-t-il un moyen d'éviter ces manipulations répétitives et de rendre le code plus concis et plus maintenable ?

    A défaut, j'ai développé un record helper générique :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    unit System.Generics.TypeEnum;
     
    interface
     
    uses
      System.SysUtils, System.Generics.Collections, System.TypInfo, System.StrUtils,
      System.Classes;
     
    type
      TEnumHelper<T: record> = record
      private
        class var FLowerCaseEnumNames: TDictionary<string, T>;
        class constructor Create;
        class destructor Destroy;
      public
        class function ToString(Value: T): string; static;
        class function FromString(const Value: string): T; static;
        class function ToArray(CaseRespect: Boolean = False): TArray<string>; static;
      end;
     
    implementation
     
    { TEnumHelper<T> }
     
    class constructor TEnumHelper<T>.Create;
    var
      EnumType: PTypeInfo;
      EnumData: PTypeData;
      EnumValue: Integer;
    begin
      EnumType := TypeInfo(T);
      if EnumType^.Kind <> tkEnumeration then
        raise EInvalidOperation.Create('TEnumHelper can only be used with enumeration types');
     
      EnumData := GetTypeData(EnumType);
      FLowerCaseEnumNames := TDictionary<string, T>.Create;
      for EnumValue := EnumData^.MinValue to EnumData^.MaxValue do
      begin
        var EnumName := GetEnumName(EnumType, EnumValue);
        var Enum: T;
        Move(EnumValue, Enum, SizeOf(T));
        FLowerCaseEnumNames.AddOrSetValue(AnsiLowerCase(EnumName), Enum);
      end;
    end;
     
    class destructor TEnumHelper<T>.Destroy;
    begin
      FLowerCaseEnumNames.Free;
    end;
     
    class function TEnumHelper<T>.ToString(Value: T): string;
    begin
      Result := GetEnumName(TypeInfo(T), Integer(PByte(@Value)^));
    end;
     
    class function TEnumHelper<T>.FromString(const Value: string): T;
    begin
      if not FLowerCaseEnumNames.TryGetValue(AnsiLowerCase(Value), Result) then
        raise EArgumentException.CreateFmt('%s : Non-existent enumerated type', [Value]);
    end;
     
    class function TEnumHelper<T>.ToArray(CaseRespect: Boolean): TArray<string>;
    var
      EnumType: PTypeInfo;
      EnumData: PTypeData;
      EnumValue: Integer;
    begin
      EnumType := TypeInfo(T);
      EnumData := GetTypeData(EnumType);
      SetLength(Result, EnumData^.MaxValue - EnumData^.MinValue + 1);
      for EnumValue := EnumData^.MinValue to EnumData^.MaxValue do
      begin
        if CaseRespect then
          Result[EnumValue - EnumData^.MinValue] := GetEnumName(EnumType, EnumValue)
        else
          Result[EnumValue - EnumData^.MinValue] := AnsiLowerCase(GetEnumName(EnumType, EnumValue));
      end;
    end;
     
     
    end.
    Ce code me permet d'implémenter mon TMyEnumtypeHelper (ci-dessus) de la façon suivante :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    { TMyEnumtypeHelper }
     
    function TMyEnumtypeHelper.FromString(Value: string): TMyEnumtype;
    begin
      Self := TEnumHelper<TMyEnumtype>.FromString(Value);
      Result := Self;
    end;
     
    class function TMyEnumtypeHelper.ToArray(CaseRespect: Boolean = False): TArray<string>;
    begin
      Result := TEnumHelper<TMyEnumtype>.ToArray(CaseRespect);  
    end;
     
    function TMyEnumtypeHelper.ToString: string;
    begin
      Result := TEnumHelper<TMyEnumtype>.ToString(Self);
    end;
    Mais cela semble à mon goût du bricolage. Auriez-vous des pistes plus judicieuses ?

  2. #2
    Expert éminent
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    Juillet 2006
    Messages
    14 086
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2006
    Messages : 14 086
    Par défaut
    GetEnumValue éviterait le tableau sinon cela ressemblant assez à ce que je pratique
    Aide via F1 - FAQ - Guide du développeur Delphi devant un problème - Pensez-y !
    Attention Troll Méchant !
    "Quand un homme a faim, mieux vaut lui apprendre à pêcher que de lui donner un poisson" Confucius
    Mieux vaut se taire et paraître idiot, Que l'ouvrir et de le confirmer !
    L'ignorance n'excuse pas la médiocrité !

    L'expérience, c'est le nom que chacun donne à ses erreurs. (Oscar Wilde)
    Il faut avoir le courage de se tromper et d'apprendre de ses erreurs

  3. #3
    Rédacteur/Modérateur
    Avatar de Andnotor
    Inscrit en
    Septembre 2008
    Messages
    5 933
    Détails du profil
    Informations personnelles :
    Localisation : Autre

    Informations forums :
    Inscription : Septembre 2008
    Messages : 5 933
    Par défaut
    Tu as GetEnumName pour la conversion en chaîne et GetEnumValue fait l'inverse. Je modifierais ton code ainsi (en ajoutant des contrôles de validité) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    type
      TEnum = class abstract
      strict private
        class procedure Validate<T>;
        class function  GetInfo<T> :PTypeInfo;
        class function  GetData<T> :TTypeData;
      public
        class function  GetName<T>(Value :T) :string;
        class function  GetValue<T>(const Value :string) :integer;
        class function  ToArray<T> :TStringDynArray;
      end;
     
    { TEnum }
     
    class function TEnum.GetData<T>: TTypeData;
    begin
      Result := GetTypeData(TypeInfo(T))^;
    end;
     
    class function TEnum.GetInfo<T>: PTypeInfo;
    begin
      Result := PTypeInfo(TypeInfo(T));
    end;
     
    class function TEnum.GetName<T>(Value: T): string;
    begin
      Validate<T>;
     
      const V = PByte(@Value)^;
     
      with GetData<T> do
        if not (V in [MinValue..MaxValue]) then
          raise EInvalidOperation.CreateFmt('Invalid enumeration id (%d) for %s', [V, GetInfo<T>.Name]);
     
      Result := GetEnumName(TypeInfo(T), V);
    end;
     
    class function TEnum.GetValue<T>(const Value: string): integer;
    begin
      Validate<T>;
     
      Result := GetEnumValue(TypeInfo(T), Value);
     
      if Result = -1 then
        raise EInvalidOperation.CreateFmt('Invalid enumeration name (%s) for %s', [Value, GetInfo<T>.Name]);
    end;
     
    class function TEnum.ToArray<T>: TStringDynArray;
    begin
      Validate<T>;
     
      with GetData<T> do
      begin
        SetLength(Result, MaxValue -MinValue +1);
     
        for var i := MinValue to MaxValue do
          Result[i] := GetEnumName(TypeInfo(T), i);
      end;
    end;
     
    class procedure TEnum.Validate<T>;
    begin
      const PInfo = GetInfo<T>;
     
      if (PInfo = nil) or (PInfo.Kind <> tkEnumeration) then
        raise EInvalidOperation.Create('TEnum can only be used with enumeration types, without custom numbering');
    end;
    Et à l'utilisation :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    const Name  = TEnum.GetName<TMyEnumtype>(abstract_factory);
    const Value = TEnum.GetValue<TMyEnumtype>('abstract_factory');
    const List  = TEnum.ToArray<TMyEnumtype>;

  4. #4
    Membre Expert

    Homme Profil pro
    Retraité
    Inscrit en
    Novembre 2007
    Messages
    3 528
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 64
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Novembre 2007
    Messages : 3 528
    Par défaut
    https://docwiki.embarcadero.com/Libr...numerationType

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    type
      TTest = (lundi, mardi, mercredi);
     
    procedure TForm18.Button1Click(Sender: TObject);
    begin
      ShowMessage(TRttiEnumerationType.GetName<TTest>(TTest.mardi));
    end;

  5. #5
    Membre éclairé

    Homme Profil pro
    Mathématicien
    Inscrit en
    Juin 2016
    Messages
    29
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Marne (Champagne Ardenne)

    Informations professionnelles :
    Activité : Mathématicien

    Informations forums :
    Inscription : Juin 2016
    Messages : 29
    Par défaut
    Je tiens à vous remercier pour votre expertise et votre aide précieuse.
    Un merci spécial à Andnotor pour avoir consacré du temps à la révision de mon code.
    Avec du recul, l'utilisation de GetEnumValue aurait été une solution plus simple. Toutefois, je suis rassuré de savoir que ma méthode initiale en Delphi respectait les bonnes pratiques.

  6. #6
    Membre éclairé

    Homme Profil pro
    Mathématicien
    Inscrit en
    Juin 2016
    Messages
    29
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Marne (Champagne Ardenne)

    Informations professionnelles :
    Activité : Mathématicien

    Informations forums :
    Inscription : Juin 2016
    Messages : 29
    Par défaut
    Je poste mon unité corrigée au cas où cela serait peut-être utile à quelqu'un

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    unit System.Generics.TypeEnum;
     
    interface
     
    uses
      System.SysUtils, System.TypInfo;
     
    type
      TEnumHelper<T: record> = record
      public
        class function ToString(Value: T): string; static;
        class function FromString(const Value: string): T; static;
        class function ToArray(CaseRespect: Boolean = False): TArray<string>; static;
      end;
     
    implementation
     
    { TEnumHelper<T> }
     
    class function TEnumHelper<T>.ToString(Value: T): string;
    begin
      Result := GetEnumName(TypeInfo(T), Integer(PByte(@Value)^));
    end;
     
    class function TEnumHelper<T>.FromString(const Value: string): T;
    var
      EnumValue: Integer;
    begin
      EnumValue := GetEnumValue(TypeInfo(T), Value);
      if EnumValue = -1 then
        raise EArgumentException.CreateFmt('%s : Non-existent enumerated type', [Value]);
      Move(EnumValue, Result, SizeOf(T));
    end;
     
    class function TEnumHelper<T>.ToArray(CaseRespect: Boolean): TArray<string>;
    var
      EnumType: PTypeInfo;
      EnumData: PTypeData;
      EnumValue: Integer;
    begin
      EnumType := TypeInfo(T);
      EnumData := GetTypeData(EnumType);
      SetLength(Result, EnumData^.MaxValue - EnumData^.MinValue + 1);
      for EnumValue := EnumData^.MinValue to EnumData^.MaxValue do
      begin
        if CaseRespect then
          Result[EnumValue - EnumData^.MinValue] := GetEnumName(EnumType, EnumValue)
        else
          Result[EnumValue - EnumData^.MinValue] := AnsiLowerCase(GetEnumName(EnumType, EnumValue));
      end;
    end;
     
    end.

    Avec l'exemple fourni dans le premier message (implémentation de TMyEnumtypeHelper), cela peut s'utiliser ainsi :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
      WriteLn(prototype.ToString);
      z := y.FromString('Builder');        // z et y sont tous les deux initialisés avec la même valeur
      y.FromString('Factory_method');      // seule la valeur Factory_methode est assignée à y
      WriteLn(y.ToString);
      WriteLn(y.FromString('Singleton').ToString);
      for var Enum in TMyEnumtype.ToArray do
        "faire quelque chose avec" y.FromString(Enum)

  7. #7
    Expert confirmé
    Avatar de qi130
    Homme Profil pro
    Expert Processus IT
    Inscrit en
    Mars 2003
    Messages
    3 932
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 64
    Localisation : France

    Informations professionnelles :
    Activité : Expert Processus IT
    Secteur : Finance

    Informations forums :
    Inscription : Mars 2003
    Messages : 3 932
    Par défaut
    Hello, et excusez le néophyte que je suis.

    Quels sont les cas d'usage de tout cela? Un exemple simple (voire simpliste) m'irait bien

    Merci d'avance.

  8. #8
    Membre Expert

    Homme Profil pro
    Retraité
    Inscrit en
    Novembre 2007
    Messages
    3 528
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 64
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Novembre 2007
    Messages : 3 528
    Par défaut
    Et en quoi cette méthode est meilleure que l'utilisation des RTTI ?

  9. #9
    Membre éclairé

    Homme Profil pro
    Mathématicien
    Inscrit en
    Juin 2016
    Messages
    29
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Marne (Champagne Ardenne)

    Informations professionnelles :
    Activité : Mathématicien

    Informations forums :
    Inscription : Juin 2016
    Messages : 29
    Par défaut
    Que dire ? Je ne suis pas un expert en Delphi comme la majorité des membres de ce forum.
    Au regard de mes maigres connaissances, il y a cependant plusieurs points qui me confortent dans l’idée d’utiliser un record générique plutôt que la RTTI.

    Tout d'abord, et comme j'ai tenté de le clarifier dans mon message précédent, peut-être de manière un peu maladroite, l'usage du record TEnumHelper<T: record> permet de réutiliser le même code pour différents types énumérés sans avoir à réécrire la logique pour chaque nouveau type. Le code est donc plus propre et plus facile à maintenir.

    Un autre avantage des génériques par rapport à la RTTI est qu’ils offrent une plus grande sécurité. En effet, avec les génériques, la vérification du type est effectuée lors de la compilation. En revanche, avec la RTTI, cette vérification n’a lieu qu’au moment de l’exécution.

    Les génériques offrent également des avantages en termes de performances. Comme les helpers sont génériques, le compilateur peut optimiser le code généré, ce qui peut conduire à des exécutions plus rapides comparées à une approche basée sur la RTTI.

    Dernier point, l’utilisation d’une méthode générique au regard de ce que j’ai exposé plus haut permet un gain de temps et donc cela permet de se concentrer sur la logique métier plutôt que sur les détails techniques.

    J'ai peut-être une vision inexacte. Je serais reconnaissant que vous me fassiez savoir si mes justifications sont valables ou non.

    Référence : https://docwiki.embarcadero.com/RADS...9n%C3%A9riques
    (Voir les chapitres intitulés : "Identification de type à l'exécution" et "Moment de l'instanciation")

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. [TPW] Conversion entre les bases
    Par Haroun01 dans le forum Turbo Pascal
    Réponses: 7
    Dernier message: 04/12/2015, 06h44
  2. Réponses: 8
    Dernier message: 04/07/2015, 23h15
  3. [Turbo Pascal] Conversion entre les bases
    Par louai dans le forum Turbo Pascal
    Réponses: 2
    Dernier message: 14/04/2011, 17h10
  4. Réponses: 2
    Dernier message: 30/06/2010, 22h35
  5. conversion entre les classes et héritage.
    Par deubelte dans le forum C++
    Réponses: 14
    Dernier message: 01/05/2010, 19h53

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo