137
votes

In the post Enum ToString, a method is described to use the custom attribute DescriptionAttribute like this:

Enum HowNice {
  [Description("Really Nice")]
  ReallyNice,
  [Description("Kinda Nice")]
  SortOfNice,
  [Description("Not Nice At All")]
  NotNice
}

And then, you call a function GetDescription, using syntax like:

GetDescription<HowNice>(NotNice); // Returns "Not Nice At All"

But that doesn't really help me when I want to simply populate a ComboBox with the values of an enum, since I cannot force the ComboBox to call GetDescription.

What I want has the following requirements:

  • Reading (HowNice)myComboBox.selectedItem will return the selected value as the enum value.
  • The user should see the user-friendly display strings, and not just the name of the enumeration values. So instead of seeing "NotNice", the user would see "Not Nice At All".
  • Hopefully, the solution will require minimal code changes to existing enumerations.

Obviously, I could implement a new class for each enum that I create, and override its ToString(), but that's a lot of work for each enum, and I'd rather avoid that.

Any ideas?

Heck, I'll even throw in a hug as a bounty :-)

21
jjnguy is correct that Java enums solve this nicely (javahowto.blogspot.com/2006/10/…), but that's of questionable relevance.Matthew Flaschen
Java Enums are a joke. Maybe they'll add Properties in 2020 :/Chad Grant
For a lighter (but arguably less-robust) solution see my thread.Gutblender

21 Answers

86
votes

ComboBox has everything you need: the FormattingEnabled property, which you should set to true, and Format event, where you'll need to place desired formatting logic. Thus,

myComboBox.FormattingEnabled = true;
myComboBox.Format += delegate(object sender, ListControlConvertEventArgs e)
    {
        e.Value = GetDescription<HowNice>((HowNice)e.Value);
    }
47
votes

Don't! Enums are primitives and not UI objects - making them serve the UI in .ToString() would be quite bad from a design standpoint. You are trying to solve the wrong problem here: the real issue is that you do not want Enum.ToString() to show up in the combo box!

Now this is a very solveable problem indeed! You create a UI object to represent your combo box items:

sealed class NicenessComboBoxItem
{
    public string Description { get { return ...; } }
    public HowNice Value { get; private set; }

    public NicenessComboBoxItem(HowNice howNice) { Value = howNice; }
}

And then just add instances of this class to your combo box's Items collection and set these properties:

comboBox.ValueMember = "Value";
comboBox.DisplayMember = "Description";
43
votes

TypeConverter. I think this is what I was looking for. All hail Simon Svensson!

[TypeConverter(typeof(EnumToStringUsingDescription))]
Enum HowNice {
  [Description("Really Nice")]
  ReallyNice,
  [Description("Kinda Nice")]
  SortOfNice,
  [Description("Not Nice At All")]
  NotNice
}

All I need to change in my current enum is add this line before their declaration.

[TypeConverter(typeof(EnumToStringUsingDescription))]

Once I do that, any enum will get displayed using the DescriptionAttribute of its fields.

Oh, and the TypeConverter would be defined like this:

public class EnumToStringUsingDescription : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        return (sourceType.Equals(typeof(Enum)));
    }

    public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
    {
        return (destinationType.Equals(typeof(String)));
    }

    public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
    {
        return base.ConvertFrom(context, culture, value);
    }

    public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
    {
        if (!destinationType.Equals(typeof(String)))
        {
            throw new ArgumentException("Can only convert to string.", "destinationType");
        }

        if (!value.GetType().BaseType.Equals(typeof(Enum)))
        {
            throw new ArgumentException("Can only convert an instance of enum.", "value");
        }

        string name = value.ToString();
        object[] attrs = 
            value.GetType().GetField(name).GetCustomAttributes(typeof(DescriptionAttribute), false);
        return (attrs.Length > 0) ? ((DescriptionAttribute)attrs[0]).Description : name;
    }
}

This helps me with my ComboBox case, but obviously doesn't actually override the ToString(). I guess I'll settle for this meanwhile...

43
votes

You could write an TypeConverter that reads specified attributes to look them up in your resources. Thus you would get multi-language support for display names without much hassle.

Look into the TypeConverter's ConvertFrom/ConvertTo methods, and use reflection to read attributes on your enum fields.

34
votes

Using your enumeration example:

using System.ComponentModel;

Enum HowNice
{
    [Description("Really Nice")]
    ReallyNice,
    [Description("Kinda Nice")]
    SortOfNice,
    [Description("Not Nice At All")]
    NotNice
}

Create an Extension:

public static class EnumExtensions
{
    public static string Description(this Enum value)
    {
        var enumType = value.GetType();
        var field = enumType.GetField(value.ToString());
        var attributes = field.GetCustomAttributes(typeof(DescriptionAttribute),
                                                   false);
        return attributes.Length == 0
            ? value.ToString()
            : ((DescriptionAttribute)attributes[0]).Description;
    }
}

Then you can use something like the following:

HowNice myEnum = HowNice.ReallyNice;
string myDesc = myEnum.Description();

See: http://www.blackwasp.co.uk/EnumDescription.aspx for more information. Credit goes to Richrd Carr for the solution

8
votes

You could make a generic struct that you could use for all of your enums that has descriptions. With implicit conversions to and from the class, your variables still works like the enum except for the ToString method:

public struct Described<T> where T : struct {

    private T _value;

    public Described(T value) {
        _value = value;
    }

    public override string ToString() {
        string text = _value.ToString();
        object[] attr =
            typeof(T).GetField(text)
            .GetCustomAttributes(typeof(DescriptionAttribute), false);
        if (attr.Length == 1) {
            text = ((DescriptionAttribute)attr[0]).Description;
        }
        return text;
    }

    public static implicit operator Described<T>(T value) {
        return new Described<T>(value);
    }

    public static implicit operator T(Described<T> value) {
        return value._value;
    }

}

Usage example:

Described<HowNice> nice = HowNice.ReallyNice;

Console.WriteLine(nice == HowNice.ReallyNice); // writes "True"
Console.WriteLine(nice); // writes "Really Nice"
5
votes

I don't think you can do it without simply binding to a different type - at least, not conveniently. Normally, even if you can't control ToString(), you can use a TypeConverter to do custom formatting - but IIRC the System.ComponentModel stuff doesn't respect this for enums.

You could bind to a string[] of the descriptions, or a something essentially like a key/value pair? (desription/value) - something like:

class EnumWrapper<T> where T : struct
{
    private readonly T value;
    public T Value { get { return value; } }
    public EnumWrapper(T value) { this.value = value; }
    public string Description { get { return GetDescription<T>(value); } }
    public override string ToString() { return Description; }

    public static EnumWrapper<T>[] GetValues()
    {
        T[] vals = (T[])Enum.GetValues(typeof(T));
        return Array.ConvertAll(vals, v => new EnumWrapper<T>(v));
    }
}

And then bind to EnumWrapper<HowNice>.GetValues()

5
votes

The best way to do this is to make a class.

class EnumWithToString {
    private string description;
    internal EnumWithToString(string desc){
        description = desc;
    }
    public override string ToString(){
        return description;
    }
}

class HowNice : EnumWithToString {

    private HowNice(string desc) : base(desc){}

    public static readonly HowNice ReallyNice = new HowNice("Really Nice");
    public static readonly HowNice KindaNice = new HowNice("Kinda Nice");
    public static readonly HowNice NotVeryNice = new HowNice("Really Mean!");
}

I believe that is the best way to do it.

When stuffed in comboboxes the pretty ToString will be shown, and the fact that no one can make any more instances of your class essentially makes it an enum.

p.s. there may need to be some slight syntax fixes, I'm not super good with C#. (Java guy)

4
votes

Not possible to override the ToString() of enums in C#. However, you can use extension methods;

public static string ToString(this HowNice self, int neverUsed)
{
    switch (self)
    {
        case HowNice.ReallyNice:
            return "Rilly, rilly nice";
            break;
    ...

Of course you will have to make an explicit call to the method, i.e;

HowNice.ReallyNice.ToString(0)

This is not a nice solution, with a switch statement and all - but it should work and hopefully whitout to many rewrites...

3
votes

Given that you'd rather not create a class for each enum, I'd recommend creating a dictionary of the enum value/display text and binding that instead.

Note that this has a dependency on the GetDescription method methods in the original post.

public static IDictionary<T, string> GetDescriptions<T>()
    where T : struct
{
    IDictionary<T, string> values = new Dictionary<T, string>();

    Type type = enumerationValue.GetType();
    if (!type.IsEnum)
    {
        throw new ArgumentException("T must be of Enum type", "enumerationValue");
    }

    //Tries to find a DescriptionAttribute for a potential friendly name
    //for the enum
    foreach (T value in Enum.GetValues(typeof(T)))
    {
        string text = value.GetDescription();

        values.Add(value, text);
    }

    return values;
}
2
votes

Following up on @scraimer answer, here is a version of the enum-to-string type converter, which supports flags as well:

    /// <summary>
/// A drop-in converter that returns the strings from 
/// <see cref="System.ComponentModel.DescriptionAttribute"/>
/// of items in an enumaration when they are converted to a string,
/// like in ToString().
/// </summary>
public class EnumToStringUsingDescription : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        return (sourceType.Equals(typeof(Enum)));
    }

    public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
    {
        return (destinationType.Equals(typeof(String)));
    }

    public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
    {
        return base.ConvertFrom(context, culture, value);
    }

    public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
    {
        if (destinationType.Equals(typeof(String)))
        {
            string name = value.ToString();
            Type effectiveType = value.GetType();          

            if (name != null)
            {
                FieldInfo fi = effectiveType.GetField(name);
                if (fi != null)
                {
                    object[] attrs =
                    fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
                    return (attrs.Length > 0) ? ((DescriptionAttribute)attrs[0]).Description : name;
                }

            }
        }

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

    /// <summary>
    /// Coverts an Enums to string by it's description. falls back to ToString.
    /// </summary>
    /// <param name="value">The value.</param>
    /// <returns></returns>
    public string EnumToString(Enum value)
    {
        //getting the actual values
        List<Enum> values = EnumToStringUsingDescription.GetFlaggedValues(value);
        //values.ToString();
        //Will hold results for each value
        List<string> results = new List<string>();
        //getting the representing strings
        foreach (Enum currValue in values)
        {
            string currresult = this.ConvertTo(null, null, currValue, typeof(String)).ToString();;
            results.Add(currresult);
        }

        return String.Join("\n",results);

    }

    /// <summary>
    /// All of the values of enumeration that are represented by specified value.
    /// If it is not a flag, the value will be the only value retured
    /// </summary>
    /// <param name="value">The value.</param>
    /// <returns></returns>
    private static List<Enum> GetFlaggedValues(Enum value)
    {
        //checking if this string is a flaged Enum
        Type enumType = value.GetType();
        object[] attributes = enumType.GetCustomAttributes(true);
        bool hasFlags = false;
        foreach (object currAttibute in attributes)
        {
            if (enumType.GetCustomAttributes(true)[0] is System.FlagsAttribute)
            {
                hasFlags = true;
                break;
            }
        }
        //If it is a flag, add all fllaged values
        List<Enum> values = new List<Enum>();
        if (hasFlags)
        {
            Array allValues = Enum.GetValues(enumType);
            foreach (Enum currValue in allValues)
            {
                if (value.HasFlag(currValue))
                {
                    values.Add(currValue);
                }
            }



        }
        else//if not just add current value
        {
            values.Add(value);
        }
        return values;
    }

}

And an extension method for using it:

    /// <summary>
    /// Converts an Enum to string by it's description. falls back to ToString
    /// </summary>
    /// <param name="enumVal">The enum val.</param>
    /// <returns></returns>
    public static string ToStringByDescription(this Enum enumVal)
    {
        EnumToStringUsingDescription inter = new EnumToStringUsingDescription();
        string str = inter.EnumToString(enumVal);
        return str;
    }
1
votes

I would write a generic class for use with any type. I've used something like this in the past:

public class ComboBoxItem<T>
{
    /// The text to display.
    private string text = "";
    /// The associated tag.
    private T tag = default(T);

    public string Text
    {
        get
        {
            return text;
        }
    }

    public T Tag
    {
        get
        {
            return tag;
        }
    }

    public override string ToString()
    {
        return text;
    }

    // Add various constructors here to fit your needs
}

On top of this, you could add a static "factory method" to create a list of combobox items given an enum type (pretty much the same as the GetDescriptions method you have there). This would save you of having to implement one entity per each enum type, and also provide a nice/logical place for the "GetDescriptions" helper method (personally I would call it FromEnum(T obj) ...

1
votes

Create a collection that contains what you need (like simple objects containing a Value property containing the HowNice enum value and a Description property containing GetDescription<HowNice>(Value) and databind the combo to that collection.

Bit like this:

Combo.DataSource = new EnumeratedValueCollection<HowNice>();
Combo.ValueMember = "Value";
Combo.DisplayMember = "Description";

when you have a collection class like this:

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

namespace Whatever.Tickles.Your.Fancy
{
    public class EnumeratedValueCollection<T> : ReadOnlyCollection<EnumeratedValue<T>>
    {
        public EnumeratedValueCollection()
            : base(ListConstructor()) { }
        public EnumeratedValueCollection(Func<T, bool> selection)
            : base(ListConstructor(selection)) { }
        public EnumeratedValueCollection(Func<T, string> format)
            : base(ListConstructor(format)) { }
        public EnumeratedValueCollection(Func<T, bool> selection, Func<T, string> format)
            : base(ListConstructor(selection, format)) { }
        internal EnumeratedValueCollection(IList<EnumeratedValue<T>> data)
            : base(data) { }

        internal static List<EnumeratedValue<T>> ListConstructor()
        {
            return ListConstructor(null, null);
        }

        internal static List<EnumeratedValue<T>> ListConstructor(Func<T, string> format)
        {
            return ListConstructor(null, format);
        }

        internal static List<EnumeratedValue<T>> ListConstructor(Func<T, bool> selection)
        {
            return ListConstructor(selection, null);
        }

        internal static List<EnumeratedValue<T>> ListConstructor(Func<T, bool> selection, Func<T, string> format)
        {
            if (null == selection) selection = (x => true);
            if (null == format) format = (x => GetDescription<T>(x));
            var result = new List<EnumeratedValue<T>>();
            foreach (T value in System.Enum.GetValues(typeof(T)))
            {
                if (selection(value))
                {
                    string description = format(value);
                    result.Add(new EnumeratedValue<T>(value, description));
                }
            }
            return result;
        }

        public bool Contains(T value)
        {
            return (Items.FirstOrDefault(item => item.Value.Equals(value)) != null);
        }

        public EnumeratedValue<T> this[T value]
        {
            get
            {
                return Items.First(item => item.Value.Equals(value));
            }
        }

        public string Describe(T value)
        {
            return this[value].Description;
        }
    }

    [System.Diagnostics.DebuggerDisplay("{Value} ({Description})")]
    public class EnumeratedValue<T>
    {
        private T value;
        private string description;
        internal EnumeratedValue(T value, string description) {
            this.value = value;
            this.description = description;
        }
        public T Value { get { return this.value; } }
        public string Description { get { return this.description; } }
    }

}

As you can see, this collection is easily customizable with lambda's to select a subset of your enumerator and/or implement a custom formatting to string instead of using the GetDescription<T>(x) function you mention.

1
votes

You could use PostSharp to target Enum.ToString and add aditionall code you want. This doesn't require any code changes.

1
votes

What you need is to turn an enum into a ReadonlyCollection and bind the collection to the combobox (or any Key-Value Pair enabled control for that matter)

First off you need a class to contain the items of the list. Since all you need is the int/string pair I suggest using an interface and a base class combo so that you can implement the functionality in any object you want:

public interface IValueDescritionItem
{
    int Value { get; set;}
    string Description { get; set;}
}

public class MyItem : IValueDescritionItem
{
    HowNice _howNice;
    string _description;

    public MyItem()
    {

    }

    public MyItem(HowNice howNice, string howNice_descr)
    {
        _howNice = howNice;
        _description = howNice_descr;
    }

    public HowNice Niceness { get { return _howNice; } }
    public String NicenessDescription { get { return _description; } }


    #region IValueDescritionItem Members

    int IValueDescritionItem.Value
    {
        get { return (int)_howNice; }
        set { _howNice = (HowNice)value; }
    }

    string IValueDescritionItem.Description
    {
        get { return _description; }
        set { _description = value; }
    }

    #endregion
}

Here is the interface and a sample class that implements it.Notice that the class' Key is strongly typed to the Enum, and that the IValueDescritionItem proprties are implemented explicitely (so the class can have whatever properties and you can CHOOSE the ones that implement the Key/Value pair.

Now the EnumToReadOnlyCollection class:

public class EnumToReadOnlyCollection<T,TEnum> : ReadOnlyCollection<T> where T: IValueDescritionItem,new() where TEnum : struct
{
    Type _type;

    public EnumToReadOnlyCollection() : base(new List<T>())
    {
        _type = typeof(TEnum);
        if (_type.IsEnum)
        {
            FieldInfo[] fields = _type.GetFields();

            foreach (FieldInfo enum_item in fields)
            {
                if (!enum_item.IsSpecialName)
                {
                    T item = new T();
                    item.Value = (int)enum_item.GetValue(null);
                    item.Description = ((ItemDescription)enum_item.GetCustomAttributes(false)[0]).Description;
                    //above line should be replaced with proper code that gets the description attribute
                    Items.Add(item);
                }
            }
        }
        else
            throw new Exception("Only enum types are supported.");
    }

    public T this[TEnum key]
    {
        get 
        {
            return Items[Convert.ToInt32(key)];
        }
    }

}

So all you need in your code is :

private EnumToReadOnlyCollection<MyItem, HowNice> enumcol;
enumcol = new EnumToReadOnlyCollection<MyItem, HowNice>();
comboBox1.ValueMember = "Niceness";
comboBox1.DisplayMember = "NicenessDescription";
comboBox1.DataSource = enumcol;

Remember that your collection is typed with MyItem so the combobox value should return an enum value if you bind to the appropriate proprtie.

I added the T this[Enum t] property to make the collection even more usefull than a simple combo consumable, for example textBox1.Text = enumcol[HowNice.ReallyNice].NicenessDescription;

You can of course chose to turn MyItem into a Key/Value class used only for this puprose effectively skipping MyItem in the type arguments of EnumToReadnlyCollection altogether, but then you'd be forced to go with int for the key (meaning getting combobox1.SelectedValue would return int and not the enum type). You work around that if you create a KeyValueItem class to replace MyItem and so on and so forth...

1
votes

Sorry for getting this old thread up.

I would go the following way to localize enum, as it can display meaningful and localized values to user, not just description, through a dropdownlist text field in this example.

First, I create a simple method called OwToStringByCulture to get localized strings from a global resources file, in this example it is BiBongNet.resx in the App_GlobalResources folder. Inside this resource file, make sure you have all strings the same as the values of the enum (ReallyNice, SortOfNice, NotNice). In this method, I pass in the parameter: resourceClassName which is usually the name of the resource file.

Next, I create a static method to fill a dropdownlist with enum as its datasource, called OwFillDataWithEnum. This method can be used with any enum later.

Then in the page with a dropdownlist called DropDownList1, I set in the Page_Load the following just one simple line of code to fill the enum to the dropdownlist.

 BiBongNet.OwFillDataWithEnum<HowNice>(DropDownList1, "BiBongNet");

That's it. I think with some simple methods like these, you can fill any list control with any enum, with not just as descriptive values but localized text to display. You can make all these methods as extension methods for better use.

Hope this help. Share to get shared!

Here are the methods:

public class BiBongNet
{

        enum HowNice
        {
            ReallyNice,
            SortOfNice,
            NotNice
        }

        /// <summary>
        /// This method is for filling a listcontrol,
        /// such as dropdownlist, listbox... 
        /// with an enum as the datasource.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="ctrl"></param>
        /// <param name="resourceClassName"></param>
        public static void OwFillDataWithEnum<T>(ListControl ctrl, string resourceClassName)
        {
            var owType = typeof(T);
            var values = Enum.GetValues(owType);
            for (var i = 0; i < values.Length; i++)
            {
                //Localize this for displaying listcontrol's text field.
                var text = OwToStringByCulture(resourceClassName, Enum.Parse(owType, values.GetValue(i).ToString()).ToString());
                //This is for listcontrol's value field
                var key = (Enum.Parse(owType, values.GetValue(i).ToString()));
                //add values of enum to listcontrol.
                ctrl.Items.Add(new ListItem(text, key.ToString()));
            }
        }

        /// <summary>
        /// Get localized strings.
        /// </summary>
        /// <param name="resourceClassName"></param>
        /// <param name="resourceKey"></param>
        /// <returns></returns>
        public static string OwToStringByCulture(string resourceClassName, string resourceKey)
        {
                return (string)HttpContext.GetGlobalResourceObject(resourceClassName, resourceKey);
        }
}
1
votes
Enum HowNice {
  [Description("Really Nice")]
  ReallyNice,
  [Description("Kinda Nice")]
  SortOfNice,
  [Description("Not Nice At All")]
  NotNice
}

To resolve this you should use an extension Method and an Array of Strings like so:

Enum HowNice {
  ReallyNice  = 0,
  SortOfNice  = 1,
  NotNice     = 2
}

internal static class HowNiceIsThis
{
 const String[] strings = { "Really Nice", "Kinda Nice", "Not Nice At All" }

 public static String DecodeToString(this HowNice howNice)
 {
   return strings[(int)howNice];
 }
}

Simple code and fast decoding.

1
votes

I tried this approach and it worked for me.

I created a wrapper class for enums and overloaded the implicit operator so that I can assign it to enum variables (in my case I had to bind an object to a ComboBox value).

You can use reflection to format the enum values the way you want to, in my case I retrieve the DisplayAttribute out of the enum values (if existent).

Hope this helps.

public sealed class EnumItem<T>
{
    T value;

    public override string ToString()
    {
        return Display;
    }

    public string Display { get; private set; }
    public T Value { get; set; }

    public EnumItem(T val)
    {
        value = val;
        Type en = val.GetType();
        MemberInfo res = en.GetMember(val.ToString())?.FirstOrDefault();
        DisplayAttribute display = res.GetCustomAttribute<DisplayAttribute>();
        Display = display != null ? String.Format(display.Name, val) : val.ToString();
    }

    public static implicit operator T(EnumItem<T> val)
    {
        return val.Value;
    }

    public static implicit operator EnumItem<T>(T val)
    {
        return new EnumItem<T>(val);
    }
}

EDIT:

Just in case, I use the following function to get the enum values that I use for the DataSource of the ComboBox

public static class Utils
{
    public static IEnumerable<EnumItem<T>> GetEnumValues<T>()
    {
        List<EnumItem<T>> result = new List<EnumItem<T>>();
        foreach (T item in Enum.GetValues(typeof(T)))
        {
            result.Add(item);
        }
        return result;
    }
}
0
votes

Once you have the GetDescription method (it needs to be global static), you could use this through an extension method:

public static string ToString(this HowNice self)
{
    return GetDescription<HowNice>(self);
}
0
votes
Enum HowNice {   
[StringValue("Really Nice")]   
ReallyNice,   
[StringValue("Kinda Nice")]   
SortOfNice,   
[StringValue("Not Nice At All")]   
NotNice 
}

Status = ReallyNice.GetDescription()
-1
votes

You can define Enum as

Enum HowNice {   
[StringValue("Really Nice")]   
ReallyNice,   
[StringValue("Kinda Nice")]   
SortOfNice,   
[StringValue("Not Nice At All")]   
NotNice 
} 

and then use HowNice.GetStringValue().