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

912

J'ai l'énumération suivante:

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

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

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

Je dois d'abord 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érateur:

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();

        //Check first in our cached results...

        //Look for our 'StringValueAttribute' 

        //in the field's custom attributes

        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 un énumérateur. Je peux ensuite l'utiliser comme ceci:

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

Bon maintenant, tout cela fonctionne comme un charme, mais je trouve que c'est beaucoup de travail. Je me demandais s'il y avait une meilleure solution pour cela.

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

user29964
la source
8
Agréable! Je peux l'utiliser pour traduire des valeurs d'énumération en chaînes localisées.
Øyvind Skaar
5
Bien que vous puissiez trouver cela de longue haleine, c'est en fait une façon assez flexible d'aller pour d'autres choses. Comme l'un de mes collègues l'a souligné, cela pourrait être utilisé dans de nombreux cas pour remplacer les
assistants
27
MSDN recommande les classes d'attributs de suffixe avec le suffixe "Attribut". Donc "class StringValueAttribute";)
serhio
14
Je suis d'accord avec @BenAlabaster, c'est en fait assez flexible. En outre, vous pouvez en faire une méthode d'extension simplement en ajoutant thisdevant la Enumdans votre méthode statique. Ensuite, vous pouvez le faire AuthenticationMethod.Forms.GetStringValue();
Justin Pihony
5
Cette approche utilise la réflexion pour lire les valeurs d'attribut et il est très lent si vous devez appeler GetStringValue () plusieurs fois dans mon expérience. Le modèle d'énumération de type sécurisé est plus rapide.
Rn222

Réponses:

868

Essayez un modèle d' énumération de type sécurisé .

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;
    }

}

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

  • ajout d'un champ statique avec mappage

    private static readonly Dictionary<string, AuthenticationMethod> instance = new Dictionary<string,AuthenticationMethod>();
    • nb Pour que l'initialisation des champs "membre enum" ne lève pas d'exception NullReferenceException lors de l'appel du constructeur d'instance, assurez-vous de placer le champ Dictionary avant les champs "membre enum" dans votre classe. En effet, 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 d'instance peut être appelé avant que tous les champs statiques aient été initialisés et avant que le constructeur statique soit appelé.
  • remplir ce mappage dans le constructeur d'instance

    instance[name] = this;
  • et ajout d' un 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();
    }
Jakub Šturc
la source
17
Cela ressemble à une énumération, mais ce n'est pas une énumération. Je peux imaginer que cela cause des problèmes intéressants si les gens commencent à comparer AuthenticationMethods. Vous devrez probablement surcharger également divers opérateurs d'égalité.
Ant
36
@Ant: Je n'ai pas à le faire. Puisque nous n'avons qu'une seule instance de chaque AuthenticationMethod, l'égalité de référence héritée d'Object fonctionne correctement.
Jakub Šturc
10
@tyriker: le compilateur le fait. Le constructeur est privé, vous ne pouvez donc pas créer de nouvelle instance. Les membres statiques ne sont pas non plus accessibles via l'instance.
Jakub Šturc
21
@Jakub Très intéressant. Je devais jouer avec pour comprendre comment l'utiliser et réaliser ses avantages. Il s'agit d'une classe publique non statique, mais ne peut pas être instanciée et vous ne pouvez accéder qu'à ses membres statiques. Fondamentalement, il se comporte comme une énumération. Mais la meilleure partie ... les membres statiques sont typés de la classe et non une chaîne générique ou int. C'est un ... [attendez] ... tapez enum sûr! Merci de m'avoir aidé à comprendre.
Tyriker
6
@kiran J'ai posté ci-dessous une version légèrement modifiée de la réponse de Jakub Šturc qui permet de l'utiliser avec des déclarations Switch-Case, donc maintenant il n'y a pas d'inconvénient à cette approche :)
deadlydog
228

Utiliser la méthode

Enum.GetName(Type MyEnumType,  object enumvariable)  

comme dans (Supposer Shipperest un Enum défini)

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

Il existe un tas d'autres méthodes statiques sur la classe Enum qui méritent également d'être étudiées ...

Charles Bretana
la source
5
Exactement. J'ai fait 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 à un ComboBox ou autre.
lc.
5
Enum.GetName reflète les noms de champ dans l'énumération - identiques à .ToString (). Si les performances sont un problème, cela peut être un problème. Je ne m'en inquiéterais pas, sauf si vous convertissez des charges d'énumérations.
Keith
8
Une autre option à considérer, si vous avez besoin d'une énumération avec des fonctionnalités supplémentaires, est de "rouler votre propre" en utilisant une structure ... vous ajoutez des propriétés nommées statiques en lecture seule pour représenter les valeurs énumérées qui sont initialisées aux constructeurs qui génèrent des instances individuelles de la structure ...
Charles Bretana le
1
alors vous pouvez ajouter tous les autres membres struct que vous souhaitez, pour implémenter la fonctionnalité que vous voulez que cette "énumération" ait ...
Charles Bretana
2
Le problème ici est que GetName n'est pas localisable. Ce n'est pas toujours une préoccupation, mais c'est quelque chose dont il faut être conscient.
Joel Coehoorn
79

Vous pouvez référencer le nom plutôt que la valeur en utilisant ToString ()

Console.WriteLine("Auth method: {0}", AuthenticationMethod.Forms.ToString());

La documentation est ici:

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

... et si vous nommez vos énumérations en cas Pascal (comme je le fais - comme ThisIsMyEnumValue = 1 etc.), vous pouvez utiliser une expression régulière très simple pour imprimer le formulaire convivial:

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

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

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

Les sorties:

Convertir ma phrase de cas Crazy Pascal en cas amical

Cela évite de courir tout autour des maisons en créant des attributs personnalisés et en les attachant à vos énumérations ou en utilisant des tables de recherche pour épouser une valeur d'énumération avec une chaîne conviviale et, mieux encore, elle est autogérée et peut être utilisée sur n'importe quelle chaîne de cas Pascal infiniment infinie plus réutilisable. Bien sûr, cela ne vous permet pas d'avoir un nom convivial différent de votre énumération que votre solution fournit.

J'aime bien votre solution d'origine pour les 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, puis vous n'auriez pas besoin de la 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 pouvez ensuite y accéder facilement directement depuis votre instance d'énumération:

Console.WriteLine(AuthenticationMethod.SSO.GetStringValue());
BenAlabaster
la source
2
Cela n'aide pas si le "nom convivial" a besoin d'un espace. Tels que «l'authentification par formulaire»
Ray Booysen
4
Assurez-vous donc que l'énumération est nommée avec des majuscules comme FormsAuthentication et insérez un espace avant les majuscules qui ne sont pas au début. Ce n'est pas sorcier d'insérer un espace dans une chaîne ...
BenAlabaster
4
L'espacement automatique des noms de cas Pascal devient problématique s'ils contiennent des abréviations qui doivent être en majuscule, XML ou GPS par exemple.
Richard Ev
2
@RichardEv, il n'y a pas de regex parfait pour cela, mais en voici un qui devrait fonctionner un peu mieux avec les abréviations. "(?!^)([^A-Z])([A-Z])", "$1 $2". Ainsi HereIsATESTdevient Here Is ATEST.
sparebytes
Pas élégants en faisant ces petits "hacks" qui sont ce qu'ils sont. J'obtiens ce que le PO dit et j'essaie de trouver une solution similaire, c'est-à-dire en utilisant l'élégance d'Enums mais en étant capable d'accéder facilement au message associé. La seule solution à laquelle je peux penser consiste à appliquer une sorte de mappage entre le nom de l'énumération et une valeur de chaîne, mais cela ne résout pas le problème de la maintenance des données de chaîne (cependant, cela est pratique pour les scénarios où vous devez avoir plusieurs régions, etc. )
Tahir Khalid
72

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

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

Le .ToString()est également assez lent 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";
        //and so on
    }
}

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


Mise à jour C # 6

Si vous pouvez utiliser C # 6, le nouvel nameofopérateur fonctionne pour les énumérations, il nameof(MyEnum.WINDOWSAUTHENTICATION)sera donc converti en "WINDOWSAUTHENTICATION"au moment de la compilation , ce qui en fait le moyen le plus rapide pour obtenir les noms d'énumérations.

Notez que cela convertira l'énumération explicite en une constante intégrée, 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"
Keith
la source
24
Vous pouvez récupérer les valeurs d'attribut une fois et les placer dans un dictionnaire <MyEnum, string> pour conserver l'aspect déclaratif.
Jon Skeet
1
Oui, c'est ce que nous avons fini par faire dans une application avec beaucoup d'énumérations lorsque nous avons découvert que le reflet était le goulot d'étranglement.
Keith
Merci Jon et Keith, j'ai fini par utiliser votre suggestion de dictionnaire. Fonctionne très bien (et rapidement!).
Helge Klein
@ JonSkeet Je sais que c'est vieux. Mais comment pourrait-on y parvenir?
user919426
2
@ user919426: Voulez-vous obtenir? Les mettre dans un dictionnaire? Créez simplement un dictionnaire, idéalement avec un initialiseur de collection ... ce n'est pas clair ce que vous demandez.
Jon Skeet
59

J'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();
        }
}

Décorez maintenant le enumavec:

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

Quand vous appelez

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

Mangesh Pimpalkar
la source
1
J'ai dû ajouter using System.ComponentModel;également, cette méthode ne fonctionne que si vous souhaitez que la valeur de chaîne soit la même que le nom de l'énumération. OP voulait une valeur différente.
elcool
2
Tu ne veux pas dire quand tu appelles AuthenticationMethod.FORMS.ToDescription()?
nicodemus13
41

Utilisez simplement la ToString()méthode

public enum any{Tomato=0,Melon,Watermelon}

Pour référencer la chaîne Tomato, utilisez simplement

any.Tomato.ToString();
chepe
la source
Sensationnel. C'était facile. Je sais que l'OP voulait ajouter des descriptions de chaînes personnalisées, mais c'est ce dont j'avais besoin. J'aurais dû savoir essayer cela, rétrospectivement, mais j'ai emprunté la route Enum.GetName.
Rafe
7
Pourquoi tout le monde complique-t-il trop cela?
Brent
18
@Brent Parce que le plus souvent, vous avez une .ToString()valeur différente de la valeur conviviale dont vous avez besoin.
Novitchi S
2
@Brent - car c'est différent de la question posée. La question qui se pose est de savoir comment obtenir cette chaîne à partir d'une variable à laquelle une valeur énumérée a été affectée. C'est dynamique au moment de l'exécution. Il s'agit de vérifier la définition du type et de la définir au moment de l'exécution.
Hogan
1
@Hogan - le ToString () fonctionne également sur les variables: any fruit = any.Tomato; string tomato = fruit.ToString();
LiborV
29

Solution très simple à cela avec .Net 4.0 et supérieur. Aucun autre code n'est nécessaire.

public enum MyStatus
{
    Active = 1,
    Archived = 2
}

Pour obtenir la chaîne, utilisez simplement:

MyStatus.Active.ToString("f");

ou

MyStatus.Archived.ToString("f");`

La valeur sera "Actif" ou "Archivé".

Pour voir les différents formats de chaîne (le "f" ci-dessus) lors de l'appel, Enum.ToStringconsultez cette page Chaînes de format d'énumération

David C
la source
28

J'utilise l'attribut Description de l'espace de noms System.ComponentModel. Décorez simplement l'énumération, puis utilisez ce code pour le récupérer:

public static string GetDescription<T>(this object enumerationValue)
            where T : struct
        {
            Type type = enumerationValue.GetType();
            if (!type.IsEnum)
            {
                throw new ArgumentException("EnumerationValue must be of Enum type", "enumerationValue");
            }

            //Tries to find a DescriptionAttribute for a potential friendly name
            //for the enum
            MemberInfo[] memberInfo = type.GetMember(enumerationValue.ToString());
            if (memberInfo != null && memberInfo.Length > 0)
            {
                object[] attrs = memberInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);

                if (attrs != null && attrs.Length > 0)
                {
                    //Pull out the description value
                    return ((DescriptionAttribute)attrs[0]).Description;
                }
            }
            //If we have no description attribute, just return the ToString of the enum
            return enumerationValue.ToString();

        }

Par exemple:

public enum Cycle : int
{        
   [Description("Daily Cycle")]
   Daily = 1,
   Weekly,
   Monthly
}

Ce code répond parfaitement aux énumérations où vous n'avez pas besoin d'un "nom convivial" et renverra uniquement le .ToString () de l'énumération.

Ray Booysen
la source
27

J'aime vraiment la réponse de Jakub Šturc, mais son inconvénient est que vous ne pouvez pas l'utiliser avec une instruction switch-case. Voici une version légèrement modifiée de sa réponse qui peut être utilisée avec une instruction switch:

public sealed class AuthenticationMethod
{
    #region This code never needs to change.
    private readonly string _name;
    public readonly Values Value;

    private AuthenticationMethod(Values value, String name){
        this._name = name;
        this.Value = value;
    }

    public override String ToString(){
        return _name;
    }
    #endregion

    public enum Values
    {
        Forms = 1,
        Windows = 2,
        SSN = 3
    }

    public static readonly AuthenticationMethod FORMS = new AuthenticationMethod (Values.Forms, "FORMS");
    public static readonly AuthenticationMethod WINDOWSAUTHENTICATION = new AuthenticationMethod (Values.Windows, "WINDOWS");
    public static readonly AuthenticationMethod SINGLESIGNON = new AuthenticationMethod (Values.SSN, "SSN");
}

Ainsi, vous bénéficiez de tous les avantages de la réponse de Jakub Šturc, et nous pouvons l'utiliser avec une instruction switch comme ceci:

var authenticationMethodVariable = AuthenticationMethod.FORMS;  // Set the "enum" value we want to use.
var methodName = authenticationMethodVariable.ToString();       // Get the user-friendly "name" of the "enum" value.

// Perform logic based on which "enum" value was chosen.
switch (authenticationMethodVariable.Value)
{
    case authenticationMethodVariable.Values.Forms: // Do something
        break;
    case authenticationMethodVariable.Values.Windows: // Do something
        break;
    case authenticationMethodVariable.Values.SSN: // Do something
        break;      
}
chien mortel
la source
Une solution plus courte serait de supprimer les énumérations {} et de conserver à la place un nombre statique d'énumérations que vous avez construites. Cela donne également l'avantage que vous n'avez pas à ajouter une nouvelle instance que vous créez à la liste d'énumérations. par exemple, public static int nextAvailable { get; private set; }alors dans le constructeurthis.Value = nextAvailable++;
kjhf
Idée intéressante @kjhf. Cependant, je crains que si quelqu'un réorganise le code, la valeur attribuée aux valeurs d'énumération peut également changer. Par exemple, cela pourrait entraîner la récupération de la mauvaise valeur d'énumération lorsque la valeur d'énumération est enregistrée dans un fichier / une base de données, l'ordre des lignes "new AuthenticationMethod (...)" est modifié (par exemple, une est supprimée), puis relancer l'application et récupérer la valeur d'énumération du fichier / base de données; la valeur enum peut ne pas correspondre à la méthode d'authentification qui a été initialement enregistrée.
deadlydog
Bon point - bien que j'espère que dans ces cas particuliers, les gens ne se fieront pas à la valeur entière de l'énumération (ou à la réorganisation du code enum.) - et cette valeur est purement utilisée comme commutateur et peut-être une alternative à .Equals () et. GetHashCode (). Si vous êtes concerné, vous pouvez toujours mettre un énorme commentaire avec "NE PAS RÉORDONNER": p
kjhf
Ne pouvez-vous pas simplement surcharger l' =opérateur pour permettre au commutateur de fonctionner? Je l'ai fait en VB et je peux maintenant l'utiliser dans une select casedéclaration.
user1318499
@ user1318499 Non, C # a des règles plus strictes autour de l'instruction switch que VB. Vous ne pouvez pas utiliser d'instances de classe pour l'instruction Case; vous ne pouvez utiliser que des primitives constantes.
deadlydog
13

J'utilise une combinaison de plusieurs des suggestions ci-dessus, combinée avec une mise en cache. Maintenant, j'ai eu l'idée d'un code que j'ai trouvé quelque part sur le net, mais je ne me souviens pas où je l'ai trouvé ou je l'ai trouvé. Donc, si quelqu'un trouve quelque chose de similaire, veuillez commenter l'attribution.

Quoi qu'il en soit, l'utilisation implique les convertisseurs de type, donc si vous vous connectez à l'interface utilisateur, cela «fonctionne». Vous pouvez étendre le modèle de Jakub pour une recherche de code rapide en initialisant le convertisseur de type dans les méthodes statiques.

L'utilisation de base ressemblerait à ceci

[TypeConverter(typeof(CustomEnumTypeConverter<MyEnum>))]
public enum MyEnum
{
    // The custom type converter will use the description attribute
    [Description("A custom description")]
    ValueWithCustomDescription,

   // This will be exposed exactly.
   Exact
}

Le code du convertisseur de type d'énumération personnalisé suit:

public class CustomEnumTypeConverter<T> : EnumConverter
    where T : struct
{
    private static readonly Dictionary<T,string> s_toString = 
      new Dictionary<T, string>();

    private static readonly Dictionary<string, T> s_toValue = 
      new Dictionary<string, T>();

    private static bool s_isInitialized;

    static CustomEnumTypeConverter()
    {
        System.Diagnostics.Debug.Assert(typeof(T).IsEnum,
          "The custom enum class must be used with an enum type.");
    }

    public CustomEnumTypeConverter() : base(typeof(T))
    {
        if (!s_isInitialized)
        {
            Initialize();
            s_isInitialized = true;
        }
    }

    protected void Initialize()
    {
        foreach (T item in Enum.GetValues(typeof(T)))
        {
            string description = GetDescription(item);
            s_toString[item] = description;
            s_toValue[description] = item;
        }
    }

    private static string GetDescription(T optionValue)
    {
        var optionDescription = optionValue.ToString();
        var optionInfo = typeof(T).GetField(optionDescription);
        if (Attribute.IsDefined(optionInfo, typeof(DescriptionAttribute)))
        {
            var attribute = 
              (DescriptionAttribute)Attribute.
                 GetCustomAttribute(optionInfo, typeof(DescriptionAttribute));
            return attribute.Description;
        }
        return optionDescription;
    }

    public override object ConvertTo(ITypeDescriptorContext context, 
       System.Globalization.CultureInfo culture, 
       object value, Type destinationType)
    {
        var optionValue = (T)value;

        if (destinationType == typeof(string) && 
            s_toString.ContainsKey(optionValue))
        {
            return s_toString[optionValue];
        }

        return base.ConvertTo(context, culture, value, destinationType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, 
       System.Globalization.CultureInfo culture, object value)
    {
        var stringValue = value as string;

        if (!string.IsNullOrEmpty(stringValue) && s_toValue.ContainsKey(stringValue))
        {
            return s_toValue[stringValue];
        }

        return base.ConvertFrom(context, culture, value);
    }
}

}

Steve Mitcham
la source
12

Dans votre question, vous n'avez jamais dit que vous aviez besoin de la valeur numérique de l'énumération n'importe où.

Si vous n'avez pas besoin d'une énumération de type chaîne (qui n'est pas un type intégral et ne peut donc pas être une base d'énumération), voici un moyen:

    static class AuthenticationMethod
    {
        public static readonly string
            FORMS = "Forms",
            WINDOWSAUTHENTICATION = "WindowsAuthentication";
    }

vous pouvez utiliser la même syntaxe que enum pour le référencer

if (bla == AuthenticationMethod.FORMS)

Ce sera un peu plus lent qu'avec des valeurs numériques (en comparant des chaînes au lieu de nombres) mais du côté positif, il n'utilise pas la réflexion (lente) pour accéder à la chaîne.

ILIA BROUDNO
la source
si vous utilisez "const" au lieu de "statique en lecture seule", vous pouvez utiliser les valeurs comme étiquettes de casse dans une instruction switch.
Ed N.
11

Comment j'ai résolu cela comme une méthode d'extension:

using System.ComponentModel;
public static string GetDescription(this Enum value)
{
    var descriptionAttribute = (DescriptionAttribute)value.GetType()
        .GetField(value.ToString())
        .GetCustomAttributes(false)
        .Where(a => a is DescriptionAttribute)
        .FirstOrDefault();

    return descriptionAttribute != null ? descriptionAttribute.Description : value.ToString();
}

Enum:

public enum OrderType
{
    None = 0,
    [Description("New Card")]
    NewCard = 1,
    [Description("Reload")]
    Refill = 2
}

Utilisation (où o.OrderType est une propriété du même nom que l'énumération):

o.OrderType.GetDescription()

Ce qui me donne une chaîne de "New Card" ou "Reload" au lieu de la valeur enum réelle NewCard et Refill.

Seconde
la source
Pour être complet, vous devez inclure une copie de votre classe DescriptionAttribute.
Bernie White
3
Bernie, DescriptionAttribute est dans System.ComponentModel
agentnega
11

Mise à jour: Visiter cette page, 8 ans plus tard, après ne pas avoir appuyé longtemps sur C #, semble que ma réponse ne soit plus la meilleure solution. J'aime vraiment la solution de convertisseur liée aux fonctions d'attribut.

Si vous lisez ceci, assurez-vous de vérifier également les autres réponses.
(indice: ils sont au-dessus de celui-ci)


Comme la plupart d'entre vous, j'ai bien aimé la réponse choisie par Jakub Šturc , mais je déteste aussi vraiment copier-coller du code et essayer de le faire le moins possible.

J'ai donc décidé que je voulais une classe EnumBase dont la plupart des fonctionnalités sont héritées / intégrées, me laissant me concentrer sur le contenu plutôt que sur le comportement.

Le problème principal de cette approche est basé sur le fait que, bien que les valeurs Enum soient des instances de type sécurisé, l'interaction se fait avec l'implémentation statique du type Enum Class. Donc, avec un peu d'aide de la magie des génériques, je pense que j'ai finalement obtenu le bon mix. J'espère que quelqu'un trouvera cela aussi utile que moi.

Je vais commencer par l'exemple de Jakub, mais en utilisant l'héritage et les génériques:

public sealed class AuthenticationMethod : EnumBase<AuthenticationMethod, int>
{
    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)
        : base( Value, Name ) { }
    public new static IEnumerable<AuthenticationMethod> All
    { get { return EnumBase<AuthenticationMethod, int>.All; } }
    public static explicit operator AuthenticationMethod(string str)
    { return Parse(str); }
}

Et voici la classe de base:

using System;
using System.Collections.Generic;
using System.Linq; // for the .AsEnumerable() method call

// E is the derived type-safe-enum class
// - this allows all static members to be truly unique to the specific
//   derived class
public class EnumBase<E, T> where E: EnumBase<E, T>
{
    #region Instance code
    public T Value { get; private set; }
    public string Name { get; private set; }

    protected EnumBase(T EnumValue, string Name)
    {
        Value = EnumValue;
        this.Name = Name;
        mapping.Add(Name, this);
    }

    public override string ToString() { return Name; }
    #endregion

    #region Static tools
    static private readonly Dictionary<string, EnumBase<E, T>> mapping;
    static EnumBase() { mapping = new Dictionary<string, EnumBase<E, T>>(); }
    protected static E Parse(string name)
    {
        EnumBase<E, T> result;
        if (mapping.TryGetValue(name, out result))
        {
            return (E)result;
        }

        throw new InvalidCastException();
    }
    // This is protected to force the child class to expose it's own static
    // method.
    // By recreating this static method at the derived class, static
    // initialization will be explicit, promising the mapping dictionary
    // will never be empty when this method is called.
    protected static IEnumerable<E> All
    { get { return mapping.Values.AsEnumerable().Cast<E>(); } }
    #endregion
}
Serrurier
la source
Vous pourrez peut-être appeler le constructeur statique dérivé à partir du constructeur statique de base. Je suis toujours à la recherche, mais jusqu'à présent, je n'ai trouvé aucun problème: stackoverflow.com/questions/55290034/…
Cory-G
10

Je suis d'accord avec Keith, mais je ne peux pas (encore) voter.

J'utilise une méthode statique et une instruction swith pour retourner exactement ce que je veux. Dans la base de données, je stocke tinyint et mon code utilise uniquement l'énumération réelle, donc les chaînes sont pour les exigences de l'interface utilisateur. Après de nombreux tests, cela a donné les meilleures performances et le plus de contrôle sur la sortie.

public static string ToSimpleString(this enum)
{
     switch (enum)
     {
         case ComplexForms:
             return "ComplexForms";
             break;
     }
}

public static string ToFormattedString(this enum)
{
     switch (enum)
     {
         case ComplexForms:
             return "Complex Forms";
             break;
     }
}

Cependant, selon certains comptes, cela conduit à un éventuel cauchemar de maintenance et à une odeur de code. J'essaie de garder un œil sur les énumérations qui sont longues et nombreuses, ou celles qui changent fréquemment. Sinon, cela a été une excellente solution pour moi.

Tony Basallo
la source
10

Si vous êtes venu ici pour mettre en œuvre un simple "Enum" mais dont les valeurs sont des chaînes au lieu des entiers, voici la solution la plus simple:

    public sealed class MetricValueList
    {
        public static readonly string Brand = "A4082457-D467-E111-98DC-0026B9010912";
        public static readonly string Name = "B5B5E167-D467-E111-98DC-0026B9010912";
    }

La mise en oeuvre:

var someStringVariable = MetricValueList.Brand;
Grinn
la source
2
Il est probablement préférable de rendre les variables consts au lieu de les utiliser static readonly.
AndyGeek
1
consts ne sont pas bonnes pour les classes accessibles au public, car elles sont cuites au moment de la compilation, vous ne pouvez pas remplacer une DLL tierce sans recompiler tout votre code avec consts.Le décalage de performance de consts vs statique en lecture seule est négligeable.
Kristian Williams
7

Lorsque je suis confronté à ce problème, il y a quelques questions auxquelles j'essaie de trouver les réponses en premier:

  • Les noms de mes valeurs d'énumération sont-ils suffisamment conviviaux à cet effet, ou dois-je fournir des noms plus conviviaux?
  • Dois-je faire un aller-retour? Autrement dit, vais-je devoir prendre des valeurs de texte et les analyser en valeurs d'énumération?
  • Est-ce quelque chose que je dois faire pour de nombreuses énumérations dans mon projet, ou juste une seule?
  • Dans quel type d'éléments d'interface utilisateur vais-je présenter ces informations - en particulier, vais-je me lier à l'interface utilisateur ou utiliser des feuilles de propriétés?
  • Doit-il être localisé?

La façon la plus simple de le faire est d' Enum.GetValueutiliser (et de prendre en charge l'aller-retour en utilisant Enum.Parse). Il est également souvent utile de créer un TypeConverter, comme le suggère Steve Mitcham, pour prendre en charge la liaison d'interface utilisateur. (Il n'est pas nécessaire d'en créer un TypeConverterlorsque vous utilisez des feuilles de propriétés, ce qui est une bonne chose à propos des feuilles de propriétés. Bien que Lord sache qu'ils ont leurs propres problèmes.)

En général, si les réponses aux questions ci-dessus suggèrent que cela ne fonctionnera pas, ma prochaine étape consiste à créer et à remplir un statique Dictionary<MyEnum, string>, ou éventuellement un Dictionary<Type, Dictionary<int, string>>. J'ai tendance à ignorer l'étape intermédiaire de décorer le code avec des attributs, car ce qui arrive généralement le lendemain, c'est la nécessité de modifier les valeurs conviviales après le déploiement (souvent, mais pas toujours, en raison de la localisation).

Robert Rossney
la source
7

Je voulais poster ceci en tant que commentaire à l'article cité ci-dessous, mais je ne pouvais pas parce que je n'ai pas assez de représentants - alors s'il vous plaît, ne votez pas. Le code contenait une erreur et je tenais à le signaler aux personnes essayant d'utiliser cette solution:

[TypeConverter(typeof(CustomEnumTypeConverter(typeof(MyEnum))]
public enum MyEnum
{
  // The custom type converter will use the description attribute
  [Description("A custom description")]
  ValueWithCustomDescription,
  // This will be exposed exactly.
  Exact
}

devrait être

[TypeConverter(typeof(CustomEnumTypeConverter<MyEnum>))]
public enum MyEnum
{
  // The custom type converter will use the description attribute
  [Description("A custom description")]
  ValueWithCustomDescription,

  // This will be exposed exactly.
  Exact
}

Brillant!

Paula Bean
la source
5

Ma variante

public struct Colors
{
    private String current;

    private static string red = "#ff0000";
    private static string green = "#00ff00";
    private static string blue = "#0000ff";

    private static IList<String> possibleColors; 

    public static Colors Red { get { return (Colors) red; } }
    public static Colors Green { get { return (Colors) green; } }
    public static Colors Blue { get { return (Colors) blue; } }

    static Colors()
    {
        possibleColors = new List<string>() {red, green, blue};
    }

    public static explicit operator String(Colors value)
    {
        return value.current;
    }

    public static explicit operator Colors(String value)
    {
        if (!possibleColors.Contains(value))
        {
            throw new InvalidCastException();
        }

        Colors color = new Colors();
        color.current = value;
        return color;
    }

    public static bool operator ==(Colors left, Colors right)
    {
        return left.current == right.current;
    }

    public static bool operator !=(Colors left, Colors right)
    {
        return left.current != right.current;
    }

    public bool Equals(Colors other)
    {
        return Equals(other.current, current);
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (obj.GetType() != typeof(Colors)) return false;
        return Equals((Colors)obj);
    }

    public override int GetHashCode()
    {
        return (current != null ? current.GetHashCode() : 0);
    }

    public override string ToString()
    {
        return current;
    }
}

Le code a l'air un peu moche, mais les utilisations de cette structure sont assez représentatives.

Colors color1 = Colors.Red;
Console.WriteLine(color1); // #ff0000

Colors color2 = (Colors) "#00ff00";
Console.WriteLine(color2); // #00ff00

// Colors color3 = "#0000ff"; // Compilation error
// String color4 = Colors.Red; // Compilation error

Colors color5 = (Colors)"#ff0000";
Console.WriteLine(color1 == color5); // True

Colors color6 = (Colors)"#00ff00";
Console.WriteLine(color1 == color6); // False

De plus, je pense que si un grand nombre de ces énumérations sont requises, la génération de code (par exemple T4) pourrait être utilisée.

Razoomnick
la source
4

Option 1:

public sealed class FormsAuth
{
     public override string ToString{return "Forms Authtentication";}
}
public sealed class WindowsAuth
{
     public override string ToString{return "Windows Authtentication";}
}

public sealed class SsoAuth
{
     public override string ToString{return "SSO";}
}

et alors

object auth = new SsoAuth(); //or whatever

//...
//...
// blablabla

DoSomethingWithTheAuth(auth.ToString());

Option 2:

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

public class MyClass
{
    private Dictionary<AuthenticationMethod, String> map = new Dictionary<AuthenticationMethod, String>();
    public MyClass()
    {
         map.Add(AuthenticationMethod.FORMS,"Forms Authentication");
         map.Add(AuthenticationMethod.WINDOWSAUTHENTICATION ,"Windows Authentication");
         map.Add(AuthenticationMethod.SINGLESIGNON ,"SSo Authentication");
    }
}
Pablo Retyk
la source
4

Si vous pensez au problème que nous essayons de résoudre, ce n'est pas du tout une énumération dont nous avons besoin. Nous avons besoin d'un objet qui permette d'associer un certain nombre de valeurs; en d'autres termes, pour définir une classe.

Le modèle d'énumération de type sûr de Jakub Šturc est la meilleure option que je vois ici.

Regarde ça:

  • Il a un constructeur privé, donc seule la classe elle-même peut définir les valeurs autorisées.
  • C'est une classe scellée donc les valeurs ne peuvent pas être modifiées par héritage.
  • Il est de type sécurisé, permettant à vos méthodes d'exiger uniquement ce type.
  • Il n'y a aucun impact de performance de réflexion encouru en accédant aux valeurs.
  • Enfin, il peut être modifié pour associer plus de deux champs ensemble, par exemple un nom, une description et une valeur numérique.
Harvo
la source
4

pour moi, l'approche pragmatique est classe dans classe, échantillon:

public class MSEModel
{
    class WITS
    {
        public const string DATE = "5005";
        public const string TIME = "5006";
        public const string MD = "5008";
        public const string ROP = "5075";
        public const string WOB = "5073";
        public const string RPM = "7001";
... 
    }
Harveyt
la source
4

J'ai créé une classe de base pour créer des énumérations sous forme de chaînes dans .NET. Il s'agit d'un seul fichier C # que vous pouvez copier et coller dans vos projets, ou installer via le package NuGet nommé StringEnum . GitHub Repo

  • Intellisense proposera le nom de l'énumération si la classe est annotée avec le commentaire xml <completitionlist>. (Fonctionne à la fois en C # et en VB)

Démo Intellisense

  • Utilisation similaire à une énumération régulière:
///<completionlist cref="HexColor"/> 
class HexColor : StringEnum<HexColor>
{
    public static readonly HexColor Blue = Create("#FF0000");
    public static readonly HexColor Green = Create("#00FF00");
    public static readonly HexColor Red = Create("#000FF");
}
    // Static Parse Method
    HexColor.Parse("#FF0000") // => HexColor.Red
    HexColor.Parse("#ff0000", caseSensitive: false) // => HexColor.Red
    HexColor.Parse("invalid") // => throws InvalidOperationException

    // Static TryParse method.
    HexColor.TryParse("#FF0000") // => HexColor.Red
    HexColor.TryParse("#ff0000", caseSensitive: false) // => HexColor.Red
    HexColor.TryParse("invalid") // => null

    // Parse and TryParse returns the preexistent instances
    object.ReferenceEquals(HexColor.Parse("#FF0000"), HexColor.Red) // => true

    // Conversion from your `StringEnum` to `string`
    string myString1 = HexColor.Red.ToString(); // => "#FF0000"
    string myString2 = HexColor.Red; // => "#FF0000" (implicit cast)

Installation:

  • Collez la classe de base StringEnum suivante dans votre projet. ( dernière version )
  • Ou installez le package StringEnum NuGet, qui est basé sur .Net Standard 1.0afin qu'il s'exécute sur .Net Core> = 1.0, .Net Framework> = 4.5, Mono> = 4.6, etc.
    /// <summary>
    /// Base class for creating string-valued enums in .NET.<br/>
    /// Provides static Parse() and TryParse() methods and implicit cast to string.
    /// </summary>
    /// <example> 
    /// <code>
    /// class Color : StringEnum &lt;Color&gt;
    /// {
    ///     public static readonly Color Blue = Create("Blue");
    ///     public static readonly Color Red = Create("Red");
    ///     public static readonly Color Green = Create("Green");
    /// }
    /// </code>
    /// </example>
    /// <typeparam name="T">The string-valued enum type. (i.e. class Color : StringEnum&lt;Color&gt;)</typeparam>
    public abstract class StringEnum<T> : IEquatable<T> where T : StringEnum<T>, new()
    {
        protected string Value;
        private static Dictionary<string, T> valueDict = new Dictionary<string, T>();
        protected static T Create(string value)
        {
            if (value == null)
                return null; // the null-valued instance is null.

            var result = new T() { Value = value };
            valueDict.Add(value, result);
            return result;
        }

        public static implicit operator string(StringEnum<T> enumValue) => enumValue.Value;
        public override string ToString() => Value;

        public static bool operator !=(StringEnum<T> o1, StringEnum<T> o2) => o1?.Value != o2?.Value;
        public static bool operator ==(StringEnum<T> o1, StringEnum<T> o2) => o1?.Value == o2?.Value;

        public override bool Equals(object other) => this.Value.Equals((other as T)?.Value ?? (other as string));
        bool IEquatable<T>.Equals(T other) => this.Value.Equals(other.Value);
        public override int GetHashCode() => Value.GetHashCode();

        /// <summary>
        /// Parse the <paramref name="value"/> specified and returns a valid <typeparamref name="T"/> or else throws InvalidOperationException.
        /// </summary>
        /// <param name="value">The string value representad by an instance of <typeparamref name="T"/>. Matches by string value, not by the member name.</param>
        /// <param name="caseSensitive">If true, the strings must match case and takes O(log n). False allows different case but is little bit slower (O(n))</param>
        public static T Parse(string value, bool caseSensitive = true)
        {
            var result = TryParse(value, caseSensitive);
            if (result == null)
                throw new InvalidOperationException((value == null ? "null" : $"'{value}'") + $" is not a valid {typeof(T).Name}");

            return result;
        }

        /// <summary>
        /// Parse the <paramref name="value"/> specified and returns a valid <typeparamref name="T"/> or else returns null.
        /// </summary>
        /// <param name="value">The string value representad by an instance of <typeparamref name="T"/>. Matches by string value, not by the member name.</param>
        /// <param name="caseSensitive">If true, the strings must match case. False allows different case but is slower: O(n)</param>
        public static T TryParse(string value, bool caseSensitive = true)
        {
            if (value == null) return null;
            if (valueDict.Count == 0) System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(T).TypeHandle); // force static fields initialization
            if (caseSensitive)
            {
                if (valueDict.TryGetValue(value, out T item))
                    return item;
                else
                    return null;
            }
            else
            {
                // slower O(n) case insensitive search
                return valueDict.FirstOrDefault(f => f.Key.Equals(value, StringComparison.OrdinalIgnoreCase)).Value;
                // Why Ordinal? => https://esmithy.net/2007/10/15/why-stringcomparisonordinal-is-usually-the-right-choice/
            }
        }
    }
Gerardo Grignoli
la source
3

Voici encore une autre façon d'accomplir la tâche d'associer des chaînes à des énumérations:

struct DATABASE {
    public enum enums {NOTCONNECTED, CONNECTED, ERROR}
    static List<string> strings =
        new List<string>() {"Not Connected", "Connected", "Error"};

    public string GetString(DATABASE.enums value) {
        return strings[(int)value];
    }
}

Cette méthode est appelée comme ceci:

public FormMain() {
    DATABASE dbEnum;

    string enumName = dbEnum.GetString(DATABASE.enums.NOTCONNECTED);
}

Vous pouvez regrouper les énumérations liées dans leur propre structure. Comme cette méthode utilise le type enum, vous pouvez utiliser Intellisense pour afficher la liste des énumérations lors de l' GetString()appel.

Vous pouvez éventuellement utiliser le nouvel opérateur sur la DATABASEstructure. Ne pas l'utiliser signifie que les cordesList ne sont pas allouées avant le premier GetString()appel.

Russ
la source
3

Beaucoup de bonnes réponses ici, mais dans mon cas, je n'ai pas résolu ce que je voulais d'une "énumération de chaînes", qui était:

  1. Utilisable dans une instruction switch, par exemple switch (myEnum)
  2. Peut être utilisé dans les paramètres de fonction, par exemple foo (type myEnum)
  3. Peut être référencé par exemple myEnum.FirstElement
  4. Je peux utiliser des chaînes, par exemple foo ("FirstElement") == foo (myEnum.FirstElement)

1,2 et 4 peuvent en fait être résolus avec un typedef C # d'une chaîne (puisque les chaînes sont commutables en c #)

3 peut être résolu par des chaînes de const statique. Donc, si vous avez les mêmes besoins, voici l'approche la plus simple:

public sealed class Types
{

    private readonly String name;

    private Types(String name)
    {
        this.name = name;

    }

    public override String ToString()
    {
        return name;
    }

    public static implicit operator Types(string str)
    {
        return new Types(str);

    }
    public static implicit operator string(Types str)
    {
        return str.ToString();
    }


    #region enum

    public const string DataType = "Data";
    public const string ImageType = "Image";
    public const string Folder = "Folder";
    #endregion

}

Cela permet par exemple:

    public TypeArgs(Types SelectedType)
    {
        Types SelectedType = SelectedType
    }

et

public TypeObject CreateType(Types type)
    {
        switch (type)
        {

            case Types.ImageType:
              //
                break;

            case Types.DataType:
             //
                break;

        }
    }

Où CreateType peut être appelé avec une chaîne ou un type. Cependant l'inconvénient est que toute chaîne est automatiquement une énumération valide , cela pourrait être modifié, mais cela nécessiterait une sorte de fonction init ... ou pourrait-elle les rendre explicitement castées en interne?

Maintenant, si une valeur int était importante pour vous (peut-être pour la vitesse de comparaison), vous pourriez utiliser quelques idées de Jakub Šturc réponse fantastique et faire quelque chose d'un peu fou, c'est mon coup de couteau:

    public sealed class Types
{
    private static readonly Dictionary<string, Types> strInstance = new Dictionary<string, Types>();
    private static readonly Dictionary<int, Types> intInstance = new Dictionary<int, Types>();

    private readonly String name;
    private static int layerTypeCount = 0;
    private int value;
    private Types(String name)
    {
        this.name = name;
        value = layerTypeCount++;
        strInstance[name] = this;
        intInstance[value] = this;
    }

    public override String ToString()
    {
        return name;
    }


    public static implicit operator Types(int val)
    {
        Types result;
        if (intInstance.TryGetValue(val, out result))
            return result;
        else
            throw new InvalidCastException();
    }

    public static implicit operator Types(string str)
    {
        Types result;
        if (strInstance.TryGetValue(str, out result))
        {
            return result;
        }
        else
        {
            result = new Types(str);
            return result;
        }

    }
    public static implicit operator string(Types str)
    {
        return str.ToString();
    }

    public static bool operator ==(Types a, Types b)
    {
        return a.value == b.value;
    }
    public static bool operator !=(Types a, Types b)
    {
        return a.value != b.value;
    }

    #region enum

    public const string DataType = "Data";
    public const string ImageType = "Image";

    #endregion

}

mais bien sûr "Types bob = 4;" n'aurait aucun sens à moins que vous ne les ayez initialisés en premier, ce qui irait à l'encontre du but ...

Mais en théorie, TypeA == TypeB serait plus rapide ...

chrispepper1989
la source
3

Si je vous comprends bien, vous pouvez simplement utiliser .ToString () pour récupérer le nom de l'énumération à partir de la valeur (en supposant qu'il est déjà converti en énumération); Si vous aviez l'intégralité nue (disons à partir d'une base de données ou quelque chose), vous pouvez d'abord le convertir en énumération. Les deux méthodes ci-dessous vous obtiendront le nom de l'énumération.

AuthenticationMethod myCurrentSetting = AuthenticationMethod.FORMS;
Console.WriteLine(myCurrentSetting); // Prints: FORMS
string name = Enum.GetNames(typeof(AuthenticationMethod))[(int)myCurrentSetting-1];
Console.WriteLine(name); // Prints: FORMS

N'oubliez pas cependant que la deuxième technique suppose que vous utilisez des entiers et que votre index est basé sur 1 (et non sur 0). La fonction GetNames est également assez lourde en comparaison, vous générez un tableau entier à chaque appel. Comme vous pouvez le voir dans la première technique, .ToString () est en fait appelé implicitement. Les deux sont déjà mentionnés dans les réponses bien sûr, j'essaie juste de clarifier les différences entre eux.

WHol
la source
3

ancien poste mais ...

La réponse à cela peut en fait être très simple. Utilisation Enum.ToString () fonction

Il y a 6 surcharges de cette fonction, vous pouvez utiliser Enum.Tostring ("F") ou Enum.ToString () pour renvoyer la valeur de chaîne. Pas besoin de s'embêter avec autre chose. Voici une démo fonctionnelle

Notez que cette solution peut ne pas fonctionner pour tous les compilateurs ( cette démo ne fonctionne pas comme prévu ) mais au moins elle fonctionne pour le dernier compilateur.

Hammad Khan
la source
2

Eh bien, après avoir lu tout ce qui précède, je pense que les gars ont trop compliqué le problème de la transformation des énumérateurs en chaînes. J'ai aimé l'idée d'avoir des attributs sur des champs énumérés mais je pense que les attributs sont principalement utilisés pour les métadonnées, mais dans votre cas, je pense que tout ce dont vous avez besoin est une sorte de localisation.

public enum Color 
{ Red = 1, Green = 2, Blue = 3}


public static EnumUtils 
{
   public static string GetEnumResourceString(object enumValue)
    {
        Type enumType = enumValue.GetType();
        string value = Enum.GetName(enumValue.GetType(), enumValue);
        string resourceKey = String.Format("{0}_{1}", enumType.Name, value);
        string result = Resources.Enums.ResourceManager.GetString(resourceKey);
        if (string.IsNullOrEmpty(result))
        {
            result = String.Format("{0}", value);
        }
        return result;
    }
}

Maintenant, si nous essayons d'appeler la méthode ci-dessus, nous pouvons l'appeler de cette façon

public void Foo()
{
  var col = Color.Red;
  Console.WriteLine (EnumUtils.GetEnumResourceString (col));
}

Il vous suffit de créer un fichier de ressources contenant toutes les valeurs de l'énumérateur et les chaînes correspondantes

Nom de la ressource Valeur de la ressource
Color_Red My String Color en rouge
Color_Blue Blueeey
Color_Green Hulk Color

Ce qui est vraiment très bien à ce sujet, c'est qu'il sera très utile si vous avez besoin que votre application soit localisée, car il vous suffit de créer un autre fichier de ressources avec votre nouvelle langue! et Voe-la!

Bormagi
la source
1

Lorsque je suis dans une telle situation, je propose la solution ci-dessous.

Et en tant que classe consommatrice, vous pourriez avoir

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MyApp.Dictionaries
{
    class Greek
    {

        public static readonly string Alpha = "Alpha";
        public static readonly string Beta = "Beta";
        public static readonly string Gamma = "Gamma";
        public static readonly string Delta = "Delta";


        private static readonly BiDictionary<int, string> Dictionary = new BiDictionary<int, string>();


        static Greek() {
            Dictionary.Add(1, Alpha);
            Dictionary.Add(2, Beta);
            Dictionary.Add(3, Gamma);
            Dictionary.Add(4, Delta);
        }

        public static string getById(int id){
            return Dictionary.GetByFirst(id);
        }

        public static int getByValue(string value)
        {
            return Dictionary.GetBySecond(value);
        }

    }
}

Et en utilisant un dictionnaire bidirectionnel: basé sur cela ( https://stackoverflow.com/a/255638/986160 ) en supposant que les clés seront associées à des valeurs uniques dans le dictionnaire et similaires à ( https://stackoverflow.com/a / 255630/986160 ) mais un peu plus élégant. Ce dictionnaire est également énumérable et vous pouvez aller et venir des entiers aux chaînes. De plus, vous n'avez pas besoin d'avoir de chaîne dans votre base de code à l'exception de cette classe.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace MyApp.Dictionaries
{

    class BiDictionary<TFirst, TSecond> : IEnumerable
    {
        IDictionary<TFirst, TSecond> firstToSecond = new Dictionary<TFirst, TSecond>();
        IDictionary<TSecond, TFirst> secondToFirst = new Dictionary<TSecond, TFirst>();

        public void Add(TFirst first, TSecond second)
        {
            firstToSecond.Add(first, second);
            secondToFirst.Add(second, first);
        }

        public TSecond this[TFirst first]
        {
            get { return GetByFirst(first); }
        }

        public TFirst this[TSecond second]
        {
            get { return GetBySecond(second); }
        }

        public TSecond GetByFirst(TFirst first)
        {
            return firstToSecond[first];
        }

        public TFirst GetBySecond(TSecond second)
        {
            return secondToFirst[second];
        }

        public IEnumerator GetEnumerator()
        {
            return GetFirstEnumerator();
        }

        public IEnumerator GetFirstEnumerator()
        {
            return firstToSecond.GetEnumerator();
        }

        public IEnumerator GetSecondEnumerator()
        {
            return secondToFirst.GetEnumerator();
        }
    }
}
Michail Michailidis
la source
1

Pour les jeux d'énumérations de chaînes plus importants, les exemples répertoriés peuvent devenir fastidieux. Si vous voulez une liste de codes d'état ou une liste d'autres énumérations basées sur des chaînes, un système d'attributs est ennuyeux à utiliser et une classe statique avec des instances d'elle-même est ennuyeuse à configurer. Pour ma propre solution, j'utilise le modèle T4 pour faciliter la création d'énumérations basées sur des chaînes. Le résultat est similaire au fonctionnement de la classe HttpMethod.

Vous pouvez l'utiliser comme ceci:

    string statusCode = ResponseStatusCode.SUCCESS; // Automatically converts to string when needed
    ResponseStatusCode codeByValueOf = ResponseStatusCode.ValueOf(statusCode); // Returns null if not found

    // Implements TypeConverter so you can use it with string conversion methods.
    var converter = System.ComponentModel.TypeDescriptor.GetConverter(typeof(ResponseStatusCode));
    ResponseStatusCode code = (ResponseStatusCode) converter.ConvertFromInvariantString(statusCode);

    // You can get a full list of the values
    bool canIterateOverValues = ResponseStatusCode.Values.Any(); 

    // Comparisons are by value of the "Name" property. Not by memory pointer location.
    bool implementsByValueEqualsEqualsOperator = "SUCCESS" == ResponseStatusCode.SUCCESS; 

Vous commencez avec un fichier Enum.tt.

<#@ include file="StringEnum.ttinclude" #>


<#+
public static class Configuration
{
    public static readonly string Namespace = "YourName.Space";
    public static readonly string EnumName = "ResponseStatusCode";
    public static readonly bool IncludeComments = true;

    public static readonly object Nodes = new
    {
        SUCCESS = "The response was successful.",
        NON_SUCCESS = "The request was not successful.",
        RESOURCE_IS_DISCONTINUED = "The resource requested has been discontinued and can no longer be accessed."
    };
}
#>

Ensuite, vous ajoutez dans votre fichier StringEnum.ttinclude.

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Reflection" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
<#@ CleanupBehavior processor="T4VSHost" CleanupAfterProcessingtemplate="true" #>

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;

namespace <#= Configuration.Namespace #>
{
    /// <summary>
    /// TypeConverter implementations allow you to use features like string.ToNullable(T).
    /// </summary>
    public class <#= Configuration.EnumName #>TypeConverter : TypeConverter
    {
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
        }

        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
        {
            var casted = value as string;

            if (casted != null)
            {
                var result = <#= Configuration.EnumName #>.ValueOf(casted);
                if (result != null)
                {
                    return result;
                }
            }

            return base.ConvertFrom(context, culture, value);
        }

        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
        {
            var casted = value as <#= Configuration.EnumName #>;
            if (casted != null && destinationType == typeof(string))
            {
                return casted.ToString();
            }

            return base.ConvertTo(context, culture, value, destinationType);
        }
    }

    [TypeConverter(typeof(<#= Configuration.EnumName #>TypeConverter))]
    public class <#= Configuration.EnumName #> : IEquatable<<#= Configuration.EnumName #>>
    {
//---------------------------------------------------------------------------------------------------
// V A L U E S _ L I S T
//---------------------------------------------------------------------------------------------------
<# Write(Helpers.PrintEnumProperties(Configuration.Nodes)); #>

        private static List<<#= Configuration.EnumName #>> _list { get; set; } = null;
        public static List<<#= Configuration.EnumName #>> ToList()
        {
            if (_list == null)
            {
                _list = typeof(<#= Configuration.EnumName #>).GetFields().Where(x => x.IsStatic && x.IsPublic && x.FieldType == typeof(<#= Configuration.EnumName #>))
                    .Select(x => x.GetValue(null)).OfType<<#= Configuration.EnumName #>>().ToList();
            }

            return _list;
        }

        public static List<<#= Configuration.EnumName #>> Values()
        {
            return ToList();
        }

        /// <summary>
        /// Returns the enum value based on the matching Name of the enum. Case-insensitive search.
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static <#= Configuration.EnumName #> ValueOf(string key)
        {
            return ToList().FirstOrDefault(x => string.Compare(x.Name, key, true) == 0);
        }


//---------------------------------------------------------------------------------------------------
// I N S T A N C E _ D E F I N I T I O N
//---------------------------------------------------------------------------------------------------      
        public string Name { get; private set; }
        public string Description { get; private set; }
        public override string ToString() { return this.Name; }

        /// <summary>
        /// Implcitly converts to string.
        /// </summary>
        /// <param name="d"></param>
        public static implicit operator string(<#= Configuration.EnumName #> d)
        {
            return d.ToString();
        }

        /// <summary>
        /// Compares based on the == method. Handles nulls gracefully.
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static bool operator !=(<#= Configuration.EnumName #> a, <#= Configuration.EnumName #> b)
        {
            return !(a == b);
        }

        /// <summary>
        /// Compares based on the .Equals method. Handles nulls gracefully.
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static bool operator ==(<#= Configuration.EnumName #> a, <#= Configuration.EnumName #> b)
        {
            return a?.ToString() == b?.ToString();
        }

        /// <summary>
        /// Compares based on the .ToString() method
        /// </summary>
        /// <param name="o"></param>
        /// <returns></returns>
        public override bool Equals(object o)
        {
            return this.ToString() == o?.ToString();
        }

        /// <summary>
        /// Compares based on the .ToString() method
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public bool Equals(<#= Configuration.EnumName #> other)
        {
            return this.ToString() == other?.ToString();
        }

        /// <summary>
        /// Compares based on the .Name property
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            return this.Name.GetHashCode();
        }
    }
}

<#+

public static class Helpers
{
        public static string PrintEnumProperties(object nodes)
        {
            string o = "";
            Type nodesTp = Configuration.Nodes.GetType();
            PropertyInfo[] props = nodesTp.GetProperties().OrderBy(p => p.Name).ToArray();

            for(int i = 0; i < props.Length; i++)
            {
                var prop = props[i];
                if (Configuration.IncludeComments)
                {
                    o += "\r\n\r\n";
                    o += "\r\n        ///<summary>";
                    o += "\r\n        /// "+Helpers.PrintPropertyValue(prop, Configuration.Nodes);
                    o += "\r\n        ///</summary>";
                }

                o += "\r\n        public static readonly "+Configuration.EnumName+" "+prop.Name+ " = new "+Configuration.EnumName+"(){ Name = \""+prop.Name+"\", Description = "+Helpers.PrintPropertyValue(prop, Configuration.Nodes)+ "};";
            }

            o += "\r\n\r\n";

            return o;
        }

        private static Dictionary<string, string> GetValuesMap()
        {
            Type nodesTp = Configuration.Nodes.GetType();
            PropertyInfo[] props= nodesTp.GetProperties();
            var dic = new Dictionary<string,string>();
            for(int i = 0; i < props.Length; i++)
            {
                var prop = nodesTp.GetProperties()[i];
                dic[prop.Name] = prop.GetValue(Configuration.Nodes).ToString();
            }
            return dic;
        }

        public static string PrintMasterValuesMap(object nodes)
        {
            Type nodesTp = Configuration.Nodes.GetType();
            PropertyInfo[] props= nodesTp.GetProperties();
            string o = "        private static readonly Dictionary<string, string> ValuesMap = new Dictionary<string, string>()\r\n        {";
            for(int i = 0; i < props.Length; i++)
            {
                var prop = nodesTp.GetProperties()[i];
                o += "\r\n            { \""+prop.Name+"\", "+(Helpers.PrintPropertyValue(prop,Configuration.Nodes)+" },");
            }
            o += ("\r\n        };\r\n");

            return o;
        }


        public static string PrintPropertyValue(PropertyInfo prop, object objInstance)
        {
            switch(prop.PropertyType.ToString()){
                case "System.Double":
                    return prop.GetValue(objInstance).ToString()+"D";
                case "System.Float":
                    return prop.GetValue(objInstance).ToString()+"F";
                case "System.Decimal":
                    return prop.GetValue(objInstance).ToString()+"M";
                case "System.Long":
                    return prop.GetValue(objInstance).ToString()+"L";
                case "System.Boolean":
                case "System.Int16":
                case "System.Int32":
                    return prop.GetValue(objInstance).ToString().ToLowerInvariant();
                case "System.String":
                    return "\""+prop.GetValue(objInstance)+"\"";
            }

            return prop.GetValue(objInstance).ToString();
        }

        public static string _ (int numSpaces)
        {
            string o = "";
            for(int i = 0; i < numSpaces; i++){
                o += " ";
            }

            return o;
        }
}
#>

Enfin, vous recompilez votre fichier Enum.tt et la sortie ressemble à ceci:

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

using System;
using System.Linq;
using System.Collections.Generic;

namespace YourName.Space
{
    public class ResponseStatusCode
    {
//---------------------------------------------------------------------------------------------------
// V A L U E S _ L I S T 
//---------------------------------------------------------------------------------------------------



        ///<summary>
        /// "The response was successful."
        ///</summary>
        public static readonly ResponseStatusCode SUCCESS = new ResponseStatusCode(){ Name = "SUCCESS", Description = "The response was successful."};


        ///<summary>
        /// "The request was not successful."
        ///</summary>
        public static readonly ResponseStatusCode NON_SUCCESS = new ResponseStatusCode(){ Name = "NON_SUCCESS", Description = "The request was not successful."};


        ///<summary>
        /// "The resource requested has been discontinued and can no longer be accessed."
        ///</summary>
        public static readonly ResponseStatusCode RESOURCE_IS_DISCONTINUED = new ResponseStatusCode(){ Name = "RESOURCE_IS_DISCONTINUED", Description = "The resource requested has been discontinued and can no longer be accessed."};


        private static List<ResponseStatusCode> _list { get; set; } = null;
        public static List<ResponseStatusCode> ToList()
        {
            if (_list == null)
            {
                _list = typeof(ResponseStatusCode).GetFields().Where(x => x.IsStatic && x.IsPublic && x.FieldType == typeof(ResponseStatusCode))
                    .Select(x => x.GetValue(null)).OfType<ResponseStatusCode>().ToList();
            }

            return _list;
        }

        public static List<ResponseStatusCode> Values()
        {
            return ToList();
        }

        /// <summary>
        /// Returns the enum value based on the matching Name of the enum. Case-insensitive search.
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static ResponseStatusCode ValueOf(string key)
        {
            return ToList().FirstOrDefault(x => string.Compare(x.Name, key, true) == 0);
        }


//---------------------------------------------------------------------------------------------------
// I N S T A N C E _ D E F I N I T I O N 
//---------------------------------------------------------------------------------------------------       
        public string Name { get; set; }
        public string Description { get; set; }
        public override string ToString() { return this.Name; }

        /// <summary>
        /// Implcitly converts to string.
        /// </summary>
        /// <param name="d"></param>
        public static implicit operator string(ResponseStatusCode d)
        {
            return d.ToString();
        }

        /// <summary>
        /// Compares based on the == method. Handles nulls gracefully.
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static bool operator !=(ResponseStatusCode a, ResponseStatusCode b)
        {
            return !(a == b);
        }

        /// <summary>
        /// Compares based on the .Equals method. Handles nulls gracefully.
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static bool operator ==(ResponseStatusCode a, ResponseStatusCode b)
        {
            return a?.ToString() == b?.ToString();
        }

        /// <summary>
        /// Compares based on the .ToString() method
        /// </summary>
        /// <param name="o"></param>
        /// <returns></returns>
        public override bool Equals(object o)
        {
            return this.ToString() == o?.ToString();
        }

        /// <summary>
        /// Compares based on the .Name property
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            return this.Name.GetHashCode();
        }
    }
}
Pangamma
la source