Can my enums have friendly names? [duplicate]

2018-12-31 23:49发布

问题:

This question already has an answer here:

  • Associating enums with strings in C# 30 answers

I have the following enum

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

Is it not possible to have enums with \"friendly names\"?

回答1:

Enum value names must follow the same naming rules as all identifiers in C#, therefore only first name is correct.



回答2:

You could use the Description attribute, as Yuriy suggested. The following extension method makes it easy to get the description for a given value of the enum:

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

You can use it like this:

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

MyEnum x = MyEnum.Foo;
string description = x.GetDescription();


回答3:

If you have the following enum:

public enum MyEnum {
    First,
    Second,
    Third
}

You can declare Extension Methods for MyEnum (like you can for any other type). I just whipped this up:

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!!\";
        }
    }
}

With this Extension Method, the following is now legal:

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

Hope this helps!!



回答4:

No, but you can use the DescriptionAttribute to accomplish what you\'re looking for.



回答5:

You can use the Description attribute to get that friendly name. You can use the code below:

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

An example of when you would want to use this method: When your enum value is EncryptionProviderType and you want enumVar.Tostring() to return \"Encryption Provider Type\".

Prerequisite: All enum members should be applied with the attribute [Description(\"String to be returned by Tostring()\")].

Example enum:

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

And in your class, you would use it like this:

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


回答6:

One problem with this trick is that description attribute cannot be localized. I do like a technique by Sacha Barber where he creates his own version of Description attribute which would pick up values from the corresponding resource manager.

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

Although the article is around a problem that\'s generally faced by WPF developers when binding to enums, you can jump directly to the part where he creates the LocalizableDescriptionAttribute.



回答7:

Some great solutions have already been posted. When I encountered this problem, I wanted to go both ways: convert an enum into a description, and convert a string matching a description into an enum.

I have two variants, slow and fast. Both convert from enum to string and string to enum. My problem is that I have enums like this, where some elements need attributes and some don\'t. I don\'t want to put attributes on elements that don\'t need them. I have about a hundred of these total currently:

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

The first method for dealing with this is slow, and is based on suggestions I saw here and around the net. It\'s slow because we are reflecting for every 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;
    }
}
}

The problem with this is that you\'re doing reflection every time. I haven\'t measured the performance hit from doing so, but it seems alarming. Worse we are computing these expensive conversions repeatedly, without caching them.

Instead we can use a static constructor to populate some dictionaries with this conversion information, then just look up this information when needed. Apparently static classes (required for extension methods) can have constructors and fields :)

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

Look how tight the conversion methods are now. The only flaw I can think of is that this requires all the converted enums to be in the current assembly. Also, I only bother with exported enums, but you could change that if you wish.

This is how to call the methods

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


回答8:

public enum myEnum
{
         ThisNameWorks, 
         This_Name_can_be_used_instead,

}


回答9:

After reading many resources regarding this topic, including StackOverFlow, I find that not all solutions are working properly. Below is our attempt to fix this.

Basically, We take the friendly name of an Enum from a DescriptionAttribute if it exists.
If it does not We use RegEx to determine the words within the Enum name and add spaces.

Next version, we will use another Attribute to flag whether we can/should take the friendly name from a localizable resource file.

Below are the test cases. Please report if you have another test case that do not pass.

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


回答10:

They follow the same naming rules as variable names. Therefore they should not contain spaces.

Also what you are suggesting would be very bad practice anyway.



回答11:

Enum names live under the same rules as normal variable names, i.e. no spaces or dots in the middle of the names... I still consider the first one to be rather friendly though...



回答12:

This is a terrible idea, but it does work.

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

Requirements

  1. Your enum names must have the same number of characters or more than the string that you want to it to be.

  2. Your enum names shouldn\'t be repeated anywhere, just in case string interning messes things up

Why this is a bad idea (a few reasons)

  1. Your enum names become ugly beause of the requirements

  2. It relies on you calling the initialization method early enough

  3. Unsafe pointers

  4. If the internal format of string changes, e.g. if the length field is moved, you\'re screwed

  5. If Enum.ToString() is ever changed so that it returns only a copy, you\'re screwed

  6. Raymond Chen will complain about your use of undocumented features, and how it\'s your fault that the CLR team couldn\'t make an optimization to cut run time by 50%, during his next .NET week.



回答13:

I suppose that you want to show your enum values to the user, therefore, you want them to have some friendly name.

Here\'s my suggestion:

Use an enum type pattern. Although it takes some effort to implement, it is really worth it.

public class MyEnum
{  
    public static readonly MyEnum Enum1=new MyEnum(\"This will work\",1);
    public static readonly MyEnum Enum2=new MyEnum(\"This.will.work.either\",2);
    public static readonly MyEnum[] All=new []{Enum1,Enum2};
    private MyEnum(string name,int value)
    {
        Name=name;
        Value=value;
    }

    public string Name{get;set;}
    public int Value{get;set;}

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


标签: c# enums