Can my enums have friendly names? [duplicate]
Asked Answered
H

13

209

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"?

Hedron answered 12/9, 2009 at 13:41 Comment(3)
check this post #1415501Pavla
What's unfriendly about ThisNameWorks?Arlie
It's "unfriendly" because he probably wants to be able to display the enum name on a form or webpage, but can't because there are no spaces.Stephenstephenie
U
77

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

Uphemia answered 12/9, 2009 at 13:43 Comment(6)
You can use the System.ComponentModel.DataAnnotations and add an display attr. (like: [Display(Name = "This Name doesn't work")])Hippomenes
Cas's comment is effectively the solution most use these days.Pilliwinks
@CasBloem not works anymore.Tenenbaum
@CasBloem Just in case anyone is trying to use Display in an old version of .NET, it's only supported as far back as .NET 4.5.2. learn.microsoft.com/en-us/dotnet/api/…Witherspoon
Answer by Thomas Levesque should be set as the accepted answer.Akee
@CasBloem if I do: EnumType.member it just displays as "member" instead of the Display Name...Faulk
C
460

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();
Cardiology answered 12/9, 2009 at 14:1 Comment(17)
I'm planning to add support for this into UnconstrainedMelody tonight.Cornered
Cool :) I'm very interested in your UnconstrainedMelody project, but I noticed something quite annoying : the compiler complains if I try to use the Enums extension methods on a non-enum type (as expected), but Intellisense doesn't take the Enum constraint into account, it shows the methods for all value types... probably a bug in VS2008Cardiology
I just checked with VS2010, the Intellisense bug mentioned above seems to be fixedCardiology
So annoying when posts don't include the non-default namespaces that they reference... System.ComponentModel and System.ReflectionElegance
@musefan, I never include them because they just add noise. Visual Studio can add them automatically with just 2 clicks...Cardiology
This should be accepted as the answer instead of the currently accepted one.Pyramidon
Nice implementation, very useful. I would suggest just one change to return string.Empty instead of null to avoid any NullReferenceException or null checks if further processing is requiredHeisenberg
Important to note that the Description attribute is not serializable.Maryleemarylin
@SonicSoul: Yeah I agree with you, it's another good reason to include them... though did you mean to direct that comment at me, or the Thomas guy?Elegance
@ThomasLevesque often times multiple namespaces match (esp with a generic class like Description). Also not everyone is using Visual Studio to write c#Odel
@Elegance good call :) sorryOdel
I'm wondering about the if (name != null) in line 5. Given that this can't be called on a non-existent enum, won't Enum.GetName always return a value?Attendance
@Attendance what makes you think it can't be called on non-existent values? This is valid: ((MyEnum)42).GetDescription(), even if there is no value 42 in MyEnum.Cardiology
Didn't consider casts to Enum. Thanks for clearing that up!Attendance
I would suggest to replace "return null" with return value.ToString(); this way Description becomes an optional attribute.Cognition
Reflection is slow. Caching this in a dictionary will speed this up 10X (tested, verified)Penumbra
@AlexfromJitbit - can you post no#s - I recently did a similar test. applied an attrib to various class props, iterated the props, found those with the attrib and output the attrib value. For 1 cls instance it took 0.000x seconds, i.e. nothing. Sure, faster is better, but then if you cache on demand 1st use, then you'd have to check the cache on each call to see if you'd already cached it, which takes some time. & it +s more code. 10 times faster than 0.000x seconds isn't worth the sacrifice to complexity/readability. In my test it was "slow" when iterating 3000+ insts @ 0.1 second total.Justicz
U
77

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

Uphemia answered 12/9, 2009 at 13:43 Comment(6)
You can use the System.ComponentModel.DataAnnotations and add an display attr. (like: [Display(Name = "This Name doesn't work")])Hippomenes
Cas's comment is effectively the solution most use these days.Pilliwinks
@CasBloem not works anymore.Tenenbaum
@CasBloem Just in case anyone is trying to use Display in an old version of .NET, it's only supported as far back as .NET 4.5.2. learn.microsoft.com/en-us/dotnet/api/…Witherspoon
Answer by Thomas Levesque should be set as the accepted answer.Akee
@CasBloem if I do: EnumType.member it just displays as "member" instead of the Display Name...Faulk
S
37

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!!

Stephenstephenie answered 12/9, 2009 at 16:12 Comment(7)
Keep in mind that the switch statement is effectively a linear search with complexity O(n) -- the worst case requires comparison with very value in the enum.Amorete
@Roy - that's not always true, sometimes (precisely when is based on an implementation detail) the C# compiler will emit a Dictionary lookup instead, see: #3366876Fillet
@John - Thanks for the info, I did not know that! ... I think it's still true that C++ compilers emit assembly that is essentially the same as a chain of if-elseif-elseif-etc. I'll have to look that up.Amorete
Roy, I could be wrong here, but I was under the impression that that is not true. While a C/C++ compiler may emit an if/else if chain, I think it can also emit a jump table if the compiler deems it appropriate. For instance, if you're switching off of a byte value, I think it will most likely just emit a jump table.Shanitashank
Nice Slick Solution, no need of attributes and the sortCloraclorinda
While you guys worry about complexity, I'm worried about adding a new enum value and forgetting to update this switch statement. I'd rather use attributes.Scaramouch
This can be achieved with Display/name attributeKalpa
G
24

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

Gloxinia answered 12/9, 2009 at 13:45 Comment(0)
E
20

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));
Erosion answered 12/9, 2009 at 13:50 Comment(1)
Nice, And how to get the enum from a description ?Adda
D
10

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.

Derryberry answered 12/9, 2009 at 16:58 Comment(0)
E
8

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>();
Expatiate answered 16/7, 2010 at 1:26 Comment(1)
Caching everything right away is bad for memory. Doing it on demand is better. I would suggest having something like EnumExtensions<T> which would make up for the backing.Hercule
T
4
public enum myEnum
{
         ThisNameWorks, 
         This_Name_can_be_used_instead,

}
Triviality answered 12/9, 2009 at 13:45 Comment(2)
Possible, but should be avoided. Doesn't follow MS naming guidelines. I think it's just available for old C developers or something :pUncrowned
I tried doing snake casing too then just do a .Replace to get your user friendly name.Faulk
B
4

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);
        }
    }
}
Bleach answered 15/9, 2011 at 10:17 Comment(0)
U
3

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.

Uncrowned answered 12/9, 2009 at 13:43 Comment(0)
D
2

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...

Duplication answered 12/9, 2009 at 13:44 Comment(0)
J
0

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.

Jowl answered 18/7, 2011 at 14:8 Comment(0)
C
-1

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;    
    }
}
Coupler answered 12/9, 2009 at 13:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.