The basis of my solution is a struct
that can wrap Enum
values and override ToString()
.
Enter the EnumWrapper
:
public struct EnumWrapper
{
private readonly Enum e;
public EnumWrapper(Enum e) {
this.e = e;
}
public static implicit operator Enum(EnumWrapper wrapper) {
return wrapper.e;
}
public static explicit operator EnumWrapper(Enum e) {
return new EnumWrapper(e);
}
public override string ToString() {
return e.ToStringFriendly();
}
}
The method ToStringFriendly()
is defined as an extension method on Enum
:
using System.Text.RegularExpressions;
public static class _Extensions
{
public static string ToStringFriendly(this Enum e)
{
string s = e.ToString();
// enforce a charset: letters, numbers, and underscores
s = Regex.Replace(s, "[^A-Za-z0-9_]", "");
// separate numbers from letters
s = Regex.Replace(s, "([a-zA-Z])([0-9])", "$1 $2");
// separate letters from numbers
s = Regex.Replace(s, "([0-9])([a-zA-Z])", "$1 $2");
// space lowercases before uppercase (word boundary)
s = Regex.Replace(s, "([a-z])([A-Z])", "$1 $2");
// see that the nice pretty capitalized words are spaced left
s = Regex.Replace(s, "(?!^)([^ _])([A-Z][a-z]+)", "$1 $2");
// replace double underscores with colon-space delimiter
s = Regex.Replace(s, "__", ": ");
// finally replace single underscores with hyphens
s = Regex.Replace(s, "_", "-");
return s;
}
}
Now, to add any Enum
value to a ComboBox, for example,
comboBox.Items.Add((EnumWrapper)MyEnum.SomeValue);
And to get it back out (after null-testing, of course):
MyEnum myValue = (MyEnum)(Enum)(EnumWrapper)comboBox.SelectedItem;
And that's it. Now what are the pros and cons of this approach?
The Pros:
- You can pass the enum values (almost) directly in and out of your controls. Just cast back and forth to/from
EnumWrapper
.
- This works nicely for all Pascal-cased
Enum
values with a few special cases. One example might be a value called MyValue__DescriptionOf123_ENUMValue
which would come out of ToStringFriendly()
as "My Value: Description of 123-ENUM Value".
- The mechanics (a
struct
and an extension method) can be written once and tucked out of the way. There is no additional coding in each Enum
. This means it works nicely as above for enums you didn't write, assuming Pascal case, which in .NET is a good assumption. Note: This is why I'd say this answer, however elegant, is not a better solution than mine, as it requires that you can add a TypeConverter
attribute to each Enum
.
The Cons:
- This doesn't support custom descriptions or multiple languages. For example a value like
Encoding.EBCDIC
will always come through as "EBCDIC" and does not allow you to manually type "Extended Binary Coded Decimal Interchange Code", much less other languages.
Future Work
One could add custom descriptions and multi-language support by changing ToStringFriendly()
to do a language-lookup for that value before de-Pascal-casing.
For more Regex fun, see this thread.