web-dev-qa-db-fra.com

Mes enums peuvent-ils avoir des noms amicaux?

J'ai le suivant enum

public enum myEnum
{
    ThisNameWorks, 
    This Name doesn't work
    Neither.does.this;
}

N'est-il pas possible d'avoir enums avec des "noms amis"?

168
JL.

Les noms de valeur Enum doivent respecter les mêmes règles de dénomination que tous les identificateurs en C #. Par conséquent, seul le prénom est correct.

77
RaYell

Vous pouvez utiliser l'attribut Description, comme suggéré par Yuriy. La méthode d'extension suivante facilite l'obtention de la description pour une valeur donnée de l'énum:

public static string GetDescription(this Enum value)
{
    Type type = value.GetType();
    string name = Enum.GetName(type, value);
    if (name != null)
    {
        FieldInfo field = type.GetField(name);
        if (field != null)
        {
            DescriptionAttribute attr = 
                   Attribute.GetCustomAttribute(field, 
                     typeof(DescriptionAttribute)) as DescriptionAttribute;
            if (attr != null)
            {
                return attr.Description;
            }
        }
    }
    return null;
}

Vous pouvez l'utiliser comme ceci:

public enum MyEnum
{
    [Description("Description for Foo")]
    Foo,
    [Description("Description for Bar")]
    Bar
}

MyEnum x = MyEnum.Foo;
string description = x.GetDescription();
369
Thomas Levesque

Si vous avez l'énumération suivante:

public enum MyEnum {
    First,
    Second,
    Third
}

Vous pouvez déclarer des méthodes d'extension pour MyEnum (comme pour tout autre type). Je viens de fouetter ceci:

namespace Extension {
    public static class ExtensionMethods {
        public static string EnumValue(this MyEnum e) {
            switch (e) {
                case MyEnum.First:
                    return "First Friendly Value";
                case MyEnum.Second:
                    return "Second Friendly Value";
                case MyEnum.Third:
                    return "Third Friendly Value";
            }
            return "Horrible Failure!!";
        }
    }
}

Avec cette méthode d'extension, ce qui suit est désormais légal:

Console.WriteLine(MyEnum.First.EnumValue());

J'espère que cela t'aides!!

33
Mark Carpenter

Non, mais vous pouvez utiliser le DescriptionAttribute pour accomplir ce que vous cherchez.

23
Yuriy Faktorovich

Vous pouvez utiliser l'attribut Description pour obtenir ce nom convivial. Vous pouvez utiliser le code ci-dessous:

public static string ToStringEnums(Enum en)
{
    Type type = en.GetType();

    MemberInfo[] memInfo = type.GetMember(en.ToString());
    if (memInfo != null && memInfo.Length > 0)
    {
        object[] attrs = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
        if (attrs != null && attrs.Length > 0)
            return ((DescriptionAttribute)attrs[0]).Description;
    }
    return en.ToString();
}

Voici un exemple d'utilisation de cette méthode: Lorsque votre valeur enum est EncryptionProviderType et que vous voulez que enumVar.Tostring() renvoie "Type de fournisseur de chiffrement".

Condition préalable: tous les membres enum doivent être appliqués avec l'attribut [Description("String to be returned by Tostring()")].

Exemple enum:

enum ExampleEnum
{
    [Description("One is one")]
    ValueOne = 1,
    [Description("Two is two")]
    ValueTow = 2
}

Et dans votre classe, vous l'utiliseriez comme ceci:

ExampleEnum enumVar = ExampleEnum.ValueOne;
Console.WriteLine(ToStringEnums(enumVar));
13
MRG

Un problème avec cette astuce est que cet attribut de description ne peut pas être localisé. J'aime bien une technique de Sacha Barber dans laquelle il crée sa propre version de l'attribut Description, qui extrairait les valeurs du gestionnaire de ressources correspondant.

http://www.codeproject.com/KB/WPF/FriendlyEnums.aspx

Bien que l'article traite d'un problème auquel sont généralement confrontés les développeurs WPF lors de la liaison à des énumérations, vous pouvez accéder directement à la partie où il crée le LocalizableDescriptionAttribute.

10
Trainee4Life

Quelques bonnes solutions ont déjà été postées. Quand j'ai rencontré ce problème, j'ai voulu aller dans les deux sens: convertir une énumération en une description et convertir une chaîne correspondant à une description en une énumération.

J'ai deux variantes, lente et rapide. Les deux convertissent énumération en chaîne et chaîne en énumération. Mon problème est que j'ai des énumérations comme celle-ci, où certains éléments ont besoin d'attributs et d'autres non. Je ne veux pas mettre d'attributs sur des éléments qui n'en ont pas besoin. J'ai environ une centaine de ces total actuellement:

public enum POS
{   
    CC, //  Coordinating conjunction
    CD, //  Cardinal Number
    DT, //  Determiner
    EX, //  Existential there
    FW, //  Foreign Word
    IN, //  Preposision or subordinating conjunction
    JJ, //  Adjective
    [System.ComponentModel.Description("WP$")]
    WPDollar, //$   Possessive wh-pronoun
    WRB, //     Wh-adverb
    [System.ComponentModel.Description("#")]
    Hash,
    [System.ComponentModel.Description("$")]
    Dollar,
    [System.ComponentModel.Description("''")]
    DoubleTick,
    [System.ComponentModel.Description("(")]
    LeftParenth,
    [System.ComponentModel.Description(")")]
    RightParenth,
    [System.ComponentModel.Description(",")]
    Comma,
    [System.ComponentModel.Description(".")]
    Period,
    [System.ComponentModel.Description(":")]
    Colon,
    [System.ComponentModel.Description("``")]
    DoubleBackTick,
    };

La première méthode pour y faire face est lente et repose sur les suggestions que j'ai vues ici et sur Internet. C'est lent parce que nous réfléchissons à chaque conversion:

using System;
using System.Collections.Generic;
namespace CustomExtensions
{

/// <summary>
/// uses extension methods to convert enums with hypens in their names to underscore and other variants
public static class EnumExtensions
{
    /// <summary>
    /// Gets the description string, if available. Otherwise returns the name of the enum field
    /// LthWrapper.POS.Dollar.GetString() yields "$", an impossible control character for enums
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public static string GetStringSlow(this Enum value)
    {
        Type type = value.GetType();
        string name = Enum.GetName(type, value);
        if (name != null)
        {
            System.Reflection.FieldInfo field = type.GetField(name);
            if (field != null)
            {
                System.ComponentModel.DescriptionAttribute attr =
                       Attribute.GetCustomAttribute(field,
                         typeof(System.ComponentModel.DescriptionAttribute)) as System.ComponentModel.DescriptionAttribute;
                if (attr != null)
                {
                    //return the description if we have it
                    name = attr.Description; 
                }
            }
        }
        return name;
    }

    /// <summary>
    /// Converts a string to an enum field using the string first; if that fails, tries to find a description
    /// attribute that matches. 
    /// "$".ToEnum<LthWrapper.POS>() yields POS.Dollar
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    /// <returns></returns>
    public static T ToEnumSlow<T>(this string value) //, T defaultValue)
    {
        T theEnum = default(T);

        Type enumType = typeof(T);

        //check and see if the value is a non attribute value
        try
        {
            theEnum = (T)Enum.Parse(enumType, value);
        }
        catch (System.ArgumentException e)
        {
            bool found = false;
            foreach (T enumValue in Enum.GetValues(enumType))
            {
                System.Reflection.FieldInfo field = enumType.GetField(enumValue.ToString());

                System.ComponentModel.DescriptionAttribute attr =
                           Attribute.GetCustomAttribute(field,
                             typeof(System.ComponentModel.DescriptionAttribute)) as System.ComponentModel.DescriptionAttribute;

                if (attr != null && attr.Description.Equals(value))
                {
                    theEnum = enumValue;
                    found = true;
                    break;

                }
            }
            if( !found )
                throw new ArgumentException("Cannot convert " + value + " to " + enumType.ToString());
        }

        return theEnum;
    }
}
}

Le problème, c’est que vous réfléchissez à chaque fois. Je n'ai pas mesuré les résultats obtenus, mais cela semble alarmant. Pire encore, nous calculons ces conversions coûteuses à plusieurs reprises, sans les mettre en cache.

Au lieu de cela, nous pouvons utiliser un constructeur statique pour renseigner certains dictionnaires avec ces informations de conversion, puis il suffit de rechercher ces informations lorsque cela est nécessaire. Apparemment, les classes statiques (requises pour les méthodes d'extension) peuvent avoir des constructeurs et des champs :)

using System;
using System.Collections.Generic;
namespace CustomExtensions
{

/// <summary>
/// uses extension methods to convert enums with hypens in their names to underscore and other variants
/// I'm not sure this is a good idea. While it makes that section of the code much much nicer to maintain, it 
/// also incurs a performance hit via reflection. To circumvent this, I've added a dictionary so all the lookup can be done once at 
/// load time. It requires that all enums involved in this extension are in this Assembly.
/// </summary>
public static class EnumExtensions
{
    //To avoid collisions, every Enum type has its own hash table
    private static readonly Dictionary<Type, Dictionary<object,string>> enumToStringDictionary = new Dictionary<Type,Dictionary<object,string>>();
    private static readonly Dictionary<Type, Dictionary<string, object>> stringToEnumDictionary = new Dictionary<Type, Dictionary<string, object>>();

    static EnumExtensions()
    {
        //let's collect the enums we care about
        List<Type> enumTypeList = new List<Type>();

        //probe this Assembly for all enums
        System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly();
        Type[] exportedTypes = Assembly.GetExportedTypes();

        foreach (Type type in exportedTypes)
        {
            if (type.IsEnum)
                enumTypeList.Add(type);
        }

        //for each enum in our list, populate the appropriate dictionaries
        foreach (Type type in enumTypeList)
        {
            //add dictionaries for this type
            EnumExtensions.enumToStringDictionary.Add(type, new Dictionary<object,string>() );
            EnumExtensions.stringToEnumDictionary.Add(type, new Dictionary<string,object>() );

            Array values = Enum.GetValues(type);

            //its ok to manipulate 'value' as object, since when we convert we're given the type to cast to
            foreach (object value in values)
            {
                System.Reflection.FieldInfo fieldInfo = type.GetField(value.ToString());

                //check for an attribute 
                System.ComponentModel.DescriptionAttribute attribute =
                       Attribute.GetCustomAttribute(fieldInfo,
                         typeof(System.ComponentModel.DescriptionAttribute)) as System.ComponentModel.DescriptionAttribute;

                //populate our dictionaries
                if (attribute != null)
                {
                    EnumExtensions.enumToStringDictionary[type].Add(value, attribute.Description);
                    EnumExtensions.stringToEnumDictionary[type].Add(attribute.Description, value);
                }
                else
                {
                    EnumExtensions.enumToStringDictionary[type].Add(value, value.ToString());
                    EnumExtensions.stringToEnumDictionary[type].Add(value.ToString(), value);
                }
            }
        }
    }

    public static string GetString(this Enum value)
    {
        Type type = value.GetType();
        string aString = EnumExtensions.enumToStringDictionary[type][value];
        return aString; 
    }

    public static T ToEnum<T>(this string value)
    {
        Type type = typeof(T);
        T theEnum = (T)EnumExtensions.stringToEnumDictionary[type][value];
        return theEnum;
    }
 }
}

Regardez comment les méthodes de conversion sont maintenant serrées. Le seul défaut auquel je puisse penser est que cela nécessite que tous les enums convertis soient dans l’Assemblée actuelle. De plus, je ne m'occupe que des enums exportés, mais vous pouvez changer cela si vous le souhaitez.

Voici comment appeler les méthodes

 string x = LthWrapper.POS.Dollar.GetString();
 LthWrapper.POS y = "PRP$".ToEnum<LthWrapper.POS>();
8
Andrew Olney

Après avoir lu de nombreuses ressources sur ce sujet, y compris StackOverFlow, je constate que toutes les solutions ne fonctionnent pas correctement. Vous trouverez ci-dessous notre tentative pour résoudre ce problème.

Fondamentalement, nous prenons le nom convivial d’un Enum d’un attribut DescriptionAttribute s’il existe.
Si ce n'est pas le cas, nous utilisons RegEx pour déterminer les mots contenus dans le nom Enum et ajouter des espaces.

Dans la prochaine version, nous utiliserons un autre attribut pour indiquer si nous pouvons/devons prendre le nom convivial d’un fichier de ressources localisable.

Vous trouverez ci-dessous les cas de test. Veuillez signaler si vous avez un autre cas de test qui ne passe pas.

public static class EnumHelper
{
    public static string ToDescription(Enum value)
    {
        if (value == null)
        {
            return string.Empty;
        }

        if (!Enum.IsDefined(value.GetType(), value))
        {
            return string.Empty;
        }

        FieldInfo fieldInfo = value.GetType().GetField(value.ToString());
        if (fieldInfo != null)
        {
            DescriptionAttribute[] attributes =
                fieldInfo.GetCustomAttributes(typeof (DescriptionAttribute), false) as DescriptionAttribute[];
            if (attributes != null && attributes.Length > 0)
            {
                return attributes[0].Description;
            }
        }

        return StringHelper.ToFriendlyName(value.ToString());
    }
}

public static class StringHelper
{
    public static bool IsNullOrWhiteSpace(string value)
    {
        return value == null || string.IsNullOrEmpty(value.Trim());
    }

    public static string ToFriendlyName(string value)
    {
        if (value == null) return string.Empty;
        if (value.Trim().Length == 0) return string.Empty;

        string result = value;

        result = string.Concat(result.Substring(0, 1).ToUpperInvariant(), result.Substring(1, result.Length - 1));

        const string pattern = @"([A-Z]+(?![a-z])|\d+|[A-Z][a-z]+|(?![A-Z])[a-z]+)+";

        List<string> words = new List<string>();
        Match match = Regex.Match(result, pattern);
        if (match.Success)
        {
            Group group = match.Groups[1];
            foreach (Capture capture in group.Captures)
            {
                words.Add(capture.Value);
            }
        }

        return string.Join(" ", words.ToArray());
    }
}


    [TestMethod]
    public void TestFriendlyName()
    {
        string[][] cases =
            {
                new string[] {null, string.Empty},
                new string[] {string.Empty, string.Empty},
                new string[] {" ", string.Empty}, 
                new string[] {"A", "A"},
                new string[] {"z", "Z"},

                new string[] {"Pascal", "Pascal"},
                new string[] {"camel", "Camel"},

                new string[] {"PascalCase", "Pascal Case"}, 
                new string[] {"ABCPascal", "ABC Pascal"}, 
                new string[] {"PascalABC", "Pascal ABC"}, 
                new string[] {"Pascal123", "Pascal 123"}, 
                new string[] {"Pascal123ABC", "Pascal 123 ABC"}, 
                new string[] {"PascalABC123", "Pascal ABC 123"}, 
                new string[] {"123Pascal", "123 Pascal"}, 
                new string[] {"123ABCPascal", "123 ABC Pascal"}, 
                new string[] {"ABC123Pascal", "ABC 123 Pascal"}, 

                new string[] {"camelCase", "Camel Case"}, 
                new string[] {"camelABC", "Camel ABC"}, 
                new string[] {"camel123", "Camel 123"}, 
            };

        foreach (string[] givens in cases)
        {
            string input = givens[0];
            string expected = givens[1];
            string output = StringHelper.ToFriendlyName(input);

            Assert.AreEqual(expected, output);
        }
    }
}
4
Mark Menchavez
public enum myEnum
{
         ThisNameWorks, 
         This_Name_can_be_used_instead,

}
4
aJ.

Ils suivent les mêmes règles de nommage que les noms de variables. Par conséquent, ils ne doivent pas contenir d'espaces.

De plus, ce que vous suggérez serait de toute façon une très mauvaise pratique.

3
TimothyP

Les noms d’énumération sont soumis aux mêmes règles que les noms de variable normaux, c’est-à-dire qu’il n’ya ni espaces ni points au milieu des noms ... Je considère toujours le premier comme plutôt sympathique ...

2
Robban

C'est une idée terrible, mais ça marche.

    public enum myEnum
{
    ThisNameWorks,
    ThisNameDoesntWork149141331,// This Name doesn't work
    NeitherDoesThis1849204824// Neither.does.this;
}

class Program
{
    private static unsafe void ChangeString(string original, string replacement)
    {
        if (original.Length < replacement.Length)
            throw new ArgumentException();

        fixed (char* pDst = original)
        fixed (char* pSrc = replacement)
        {
            // Update the length of the original string
            int* lenPtr = (int*)pDst;
            lenPtr[-1] = replacement.Length;

            // Copy the characters
            for (int i = 0; i < replacement.Length; i++)
                pDst[i] = pSrc[i];
        }
    }

    public static unsafe void Initialize()
    {
        ChangeString(myEnum.ThisNameDoesntWork149141331.ToString(), "This Name doesn't work");
        ChangeString(myEnum.NeitherDoesThis1849204824.ToString(), "Neither.does.this");
    }

    static void Main(string[] args)
    {
        Console.WriteLine(myEnum.ThisNameWorks);
        Console.WriteLine(myEnum.ThisNameDoesntWork149141331);
        Console.WriteLine(myEnum.NeitherDoesThis1849204824);

        Initialize();

        Console.WriteLine(myEnum.ThisNameWorks);
        Console.WriteLine(myEnum.ThisNameDoesntWork149141331);
        Console.WriteLine(myEnum.NeitherDoesThis1849204824);
    }

Exigences

  1. Vos noms enum doivent avoir le même nombre de caractères ou plus que la chaîne que vous souhaitez lui attribuer.

  2. Vos noms enum ne devraient pas être répétés n'importe où, juste au cas où une chaîne interner gâcherait les choses

Pourquoi c'est une mauvaise idée (quelques raisons)

  1. Vos noms enum deviennent laids en raison des exigences

  2. Il est important que vous appeliez la méthode d'initialisation suffisamment tôt

  3. Pointeurs dangereux

  4. Si le format interne de la chaîne change, par exemple, si le champ de longueur est déplacé, vous êtes vissé

  5. Si Enum.ToString () est modifié pour qu'il ne retourne qu'une copie, vous êtes foutu

  6. Raymond Chen se plaindra de votre utilisation de fonctionnalités non documentées et de votre faute, car l'équipe CLR n'a pas pu optimiser la réduction du temps d'exécution de 50% lors de sa prochaine semaine .NET.

0
Jamie