945 votes

Représentation sous forme de chaîne d'une Enum

J'ai l'énumération suivante:

public enum AuthenticationMethod
{
    FORMS = 1,
    WINDOWSAUTHENTICATION = 2,
    SINGLESIGNON = 3
}

Le problème, cependant, est que j'ai besoin du mot "FORMS" lorsque je demande AuthenticationMethod.FORMS et non l'identifiant 1.

J'ai trouvé la solution suivante pour ce problème (lien):

Tout d'abord, je dois créer un attribut personnalisé appelé "StringValue":

public class StringValue : System.Attribute
{
    private readonly string _value;

    public StringValue(string value)
    {
        _value = value;
    }

    public string Value
    {
        get { return _value; }
    }

}

Ensuite, je peux ajouter cet attribut à mon énumération:

public enum AuthenticationMethod
{
    [StringValue("FORMS")]
    FORMS = 1,
    [StringValue("WINDOWS")]
    WINDOWSAUTHENTICATION = 2,
    [StringValue("SSO")]
    SINGLESIGNON = 3
}

Et bien sûr j'ai besoin de quelque chose pour récupérer cette StringValue:

public static class StringEnum
{
    public static string GetStringValue(Enum value)
    {
        string output = null;
        Type type = value.GetType();

        //Vérifier d'abord dans nos résultats mis en cache...

        //Rechercher notre 'StringValueAttribute' 

        //dans les attributs personnalisés du champ

        FieldInfo fi = type.GetField(value.ToString());
        StringValue[] attrs =
           fi.GetCustomAttributes(typeof(StringValue),
                                   false) as StringValue[];
        if (attrs.Length > 0)
        {
            output = attrs[0].Value;
        }

        return output;
    }
}

Bon maintenant j'ai les outils pour obtenir une valeur de chaîne pour une énumération. Je peux alors l'utiliser comme ceci:

string valueOfAuthenticationMethod = StringEnum.GetStringValue(AuthenticationMethod.FORMS);

D'accord maintenant tout cela fonctionne comme un charme mais je trouve que c'est beaucoup de travail. Je me demandais s'il existe une meilleure solution pour cela.

J'ai aussi essayé quelque chose avec un dictionnaire et des propriétés statiques mais ce n'était pas mieux non plus.

5 votes

Alors que vous pourriez trouver cela long, c'est en fait une façon assez flexible d'aller pour d'autres choses. Comme l'a souligné l'un de mes collègues, cela pourrait être utilisé dans de nombreux cas pour remplacer les Enum Helpers qui mapent les codes de base de données aux valeurs d'enum etc...

3 votes

Il s'agit d'une "Énumération", pas d'un "Énumérateur".

29 votes

MSDN recommande de suffixer les attributs de classe avec le suffixe "Attribute". Donc "class StringValueAttribute" ;)

884voto

Jakub Šturc Points 12549

Essayez le modèle type-safe-enum.

public sealed class AuthenticationMethod {

    private readonly String name;
    private readonly int value;

    public static readonly AuthenticationMethod FORMS = new AuthenticationMethod (1, "FORMS");
    public static readonly AuthenticationMethod WINDOWSAUTHENTICATION = new AuthenticationMethod (2, "WINDOWS");
    public static readonly AuthenticationMethod SINGLESIGNON = new AuthenticationMethod (3, "SSN");        

    private AuthenticationMethod(int value, String name){
        this.name = name;
        this.value = value;
    }

    public override String ToString(){
        return name;
    }

}

Mise à jour La conversion de type explicite (ou implicite) peut être effectuée en

  • ajoutant un champ statique avec un mapping

    private static readonly Dictionary instance = new Dictionary();
    • n.b. Afin que l'initialisation des champs du "membre de l'énumération" ne lance pas une NullReferenceException lors de l'appel du constructeur de l'instance, assurez-vous de placer le champ Dictionary avant les champs du "membre de l'énumération" dans votre classe. Cela est dû au fait que les initialiseurs de champs statiques sont appelés dans l'ordre de déclaration, et avant le constructeur statique, créant la situation étrange et nécessaire mais déroutante que le constructeur de l'instance peut être appelé avant que tous les champs statiques n'aient été initialisés, et avant que le constructeur statique ne soit appelé.
  • remplissant ce mapping dans le constructeur de l'instance

    instance[name] = this;
  • et en ajoutant opérateur de conversion de type défini par l'utilisateur

    public static explicit operator AuthenticationMethod(string str)
    {
        AuthenticationMethod result;
        if (instance.TryGetValue(str, out result))
            return result;
        else
            throw new InvalidCastException();
    }

22 votes

Il ressemble à une énumération mais ce n'en est pas une. Je peux imaginer que cela pourrait causer des problèmes intéressants si les gens commencent à essayer de comparer les méthodes d'authentification. Vous devez probablement surcharger divers opérateurs d'égalité aussi.

38 votes

@Ant: Je n'ai pas besoin de le faire. Puisque nous n'avons qu'une seule instance de chaque AuthenticationMethod, l'égalité de référence héritée de Object fonctionne bien.

0 votes

+1 J'ai déjà utilisé cette solution auparavant. Comme un enum puissant. Je suppose que vous ne pouvez pas l'utiliser comme valeurs dans une instruction switch, n'est-ce pas ? De plus, la seule fois où vous auriez à vous soucier du problème d'égalité serait si cela était déclaré comme une struct plutôt qu'une classe.

235voto

Charles Bretana Points 59899

Utilisez la méthode

Enum.GetName(Type MyEnumType, object enumvariable)  

comme dans (Supposez que Shipper est un Enum défini)

Shipper x = Shipper.FederalExpress;
string s = Enum.GetName(typeof(Shipper), x);

Il existe toute une série d'autres méthodes statiques sur la classe Enum qui valent la peine d'être explorées aussi...

5 votes

Exactement. J'ai bien créé un attribut personnalisé pour une description de chaîne, mais c'est parce que je veux une version conviviale (avec des espaces et d'autres caractères spéciaux) qui peut être facilement liée à une ComboBox ou similaire.

5 votes

Enum.GetName reflète les noms des champs dans l'énumération - tout comme le .ToString(). Si la performance est un problème, cela peut être un problème. Je ne m'en ferais pas à moins que vous ne convertissiez des tas d'énumérations.

8 votes

Une autre option à considérer, si vous avez besoin d'une énumération avec une fonctionnalité supplémentaire, est de "créer la vôtre" en utilisant une structure... vous ajoutez des propriétés nommées static readonly pour représenter les valeurs de l'énumération qui sont initialisées avec des constructeurs qui génèrent des instances individuelles de la structure...

81voto

BenAlabaster Points 20189

Vous pouvez faire référence au nom plutôt qu'à la valeur en utilisant ToString()

Console.WriteLine("Méthode d'authentification : {0}", AuthenticationMethod.Forms.ToString());

La documentation est ici:

http://msdn.microsoft.com/fr-fr/library/16c1xs4z.aspx

... et si vous nommez vos énumérations en Pascal Case (comme je le fais - telles que ThisIsMyEnumValue = 1, etc.), vous pourriez utiliser une expression régulière très simple pour afficher la forme conviviale:

static string ToFriendlyCase(this string EnumString)
{
    return Regex.Replace(EnumString, "(?!^)([A-Z])", " $1");
}

qui peut facilement être appelée à partir de n'importe quelle chaîne:

Console.WriteLine("ConvertMyCrazyPascalCaseSentenceToFriendlyCase".ToFriendlyCase());

Résultat:

Convert My Crazy Pascal Case Sentence To Friendly Case

Cela évite de courir dans tous les sens en créant des attributs personnalisés et en les attachant à vos énumérations ou en utilisant des tables de recherche pour associer une valeur d'énumération à une chaîne conviviale et, surtout, c'est autogéré et peut être utilisé sur n'importe quelle chaîne en Pascal Case, ce qui le rend infiniment plus réutilisable. Bien sûr, cela ne vous permet pas d'avoir un nom convivial différent de votre énumération, ce que votre solution fournit.

J'aime cependant votre solution initiale pour des scénarios plus complexes. Vous pourriez pousser votre solution un peu plus loin et faire de votre GetStringValue une méthode d'extension de votre énumération, et ensuite vous n'auriez pas besoin de le référencer comme StringEnum.GetStringValue...

public static string GetStringValue(this AuthenticationMethod value)
{
  string output = null;
  Type type = value.GetType();
  FieldInfo fi = type.GetField(value.ToString());
  StringValue[] attrs = fi.GetCustomAttributes(typeof(StringValue), false) as StringValue[];
  if (attrs.Length > 0)
    output = attrs[0].Value;
  return output;
}

Vous pourriez y accéder facilement directement depuis votre instance d'énumération:

Console.WriteLine(AuthenticationMethod.SSO.GetStringValue());

2 votes

Cela ne fonctionne pas si le "nom convivial" a besoin d'un espace. Comme "l'authentification des formulaires"

4 votes

Donc, assurez-vous que l'énumération est nommée avec des majuscules comme FormsAuthentication et insérez un espace avant chaque lettre majuscule qui ne se trouve pas au début. Ce n'est pas de la fusée science pour insérer un espace dans une chaîne...

4 votes

L'espacement automatique des noms en Pascal Case pose problème s'ils contiennent des abréviations qui devraient être en majuscules, XML ou GPS par exemple.

74voto

Keith Points 46288

Malheureusement, la réflexion pour obtenir les attributs sur les énumérations est assez lente :

Voir cette question : Quelqu'un connaît-il un moyen rapide d'accéder aux attributs personnalisés sur une valeur d'énumération ?

La méthode .ToString() est également assez lente sur les énumérations.

Vous pouvez cependant écrire des méthodes d'extension pour les énumérations :

public static string GetName( this MyEnum input ) {
    switch ( input ) {
        case MyEnum.WINDOWSAUTHENTICATION:
            return "Windows";
        //et ainsi de suite
    }
}

Ce n'est pas génial, mais ce sera rapide et ne nécessitera pas la réflexion pour les attributs ou le nom du champ.


Mise à jour C#6

Si vous utilisez C#6, alors le nouvel opérateur nameof fonctionne pour les énumérations, donc nameof(MyEnum.WINDOWSAUTHENTICATION) sera converti en "WINDOWSAUTHENTICATION" au moment de la compilation, en en faisant la manière la plus rapide d'obtenir les noms des énumérations.

Notez que cela convertira l'énumération explicite en une constante en ligne, donc cela ne fonctionne pas pour les énumérations que vous avez dans une variable. Donc :

nameof(AuthenticationMethod.FORMS) == "FORMS"

Mais...

var myMethod = AuthenticationMethod.FORMS;
nameof(myMethod) == "myMethod"

24 votes

Vous pouvez récupérer les valeurs d'attribut une fois et les placer dans un `Dictionary` pour conserver l'aspect déclaratif.

1 votes

Oui, c'est ce que nous avons fini par faire dans une application avec beaucoup d'énumérations lorsque nous avons découvert que la réflexion était le goulot d'étranglement.

0 votes

Merci Jon et Keith, j'ai fini par utiliser votre suggestion de dictionnaire. Cela fonctionne très bien (et rapidement!).

59voto

Mangesh Pimpalkar Points 2263

Je utilise une méthode d'extension:

public static class AttributesHelperExtension
    {
        public static string ToDescription(this Enum value)
        {
            var da = (DescriptionAttribute[])(value.GetType().GetField(value.ToString())).GetCustomAttributes(typeof(DescriptionAttribute), false);
            return da.Length > 0 ? da[0].Description : value.ToString();
        }
}

Maintenant décorez l'enum avec:

public enum AuthenticationMethod
{
    [Description("FORMS")]
    FORMS = 1,
    [Description("WINDOWSAUTHENTICATION")]
    WINDOWSAUTHENTICATION = 2,
    [Description("SINGLESIGNON ")]
    SINGLESIGNON = 3
}

Quand vous appelez

AuthenticationMethod.FORMS.ToDescription() vous obtiendrez "FORMS".

1 votes

J'ai dû ajouter using System.ComponentModel;. De plus, cette méthode ne fonctionne que si vous souhaitez que la valeur de la chaîne soit identique au nom de l'énumération. OP voulait une valeur différente.

2 votes

Ne voulez-vous pas dire lorsque vous appelez AuthenticationMethod.FORMS.ToDescription() ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X