Using alternate descriptions for enumeration members
The last two articles (here and here) described creating a custom type converter for converting units of measurement.
However, what happens when you want to display or convert to/from alternative representations? For example, consider the enum below.
internal enum Unit
{
cm,
mm,
pt,
px
}
Apart from the fact such an enum more than likely doesn't match any coding standards you use, what happens when you want to include percentages in the mix? Not many languages are going to let you use % as a symbol name!
So we rewrite the enum to make more sense, in which case you might have this:
internal enum Unit
{
Centimetre,
Millimetre,
Point,
Pixel,
Percent
}
Great! Except... your users want to see cm, px, % etc. Now what?
The manual way
Well, you could create a function which takes a unit, and manually checks the values and returns an appropriate value, for example:
public string GetUnitSuffix(Unit unit)
{
string result;
switch (unit)
{
case Unit.Centimetre:
result = "cm";
break;
...
}
return result;
}
While this would certainly work, it means you have to duplicate this code for every enum you wish to have alternate descriptions for. Not to mention, should you add a new member to the enum, you have to remember to update this function. More than likely, you also want a sister version of this function which accepts the string version, and returns the enum value.
The automatic way
A better way would be to tag each enum member with an appropriate description, then you can use reflection to scan your enum members and perform automatic to and from conversions.
In this example, I'm going to use the DescriptionAttribute
from the System.ComponentModel
namespace, although depending
on what you're trying to do, a custom attribute may be better -
that's not exactly what this attribute was intended for!
First, decorate your enum with the attribute.
internal enum Unit
{
[Description("cm")]
Centimetre,
[Description("mm")]
Millimetre,
[Description("pt")]
Point,
[Description("px")]
Pixel,
[Description("%")]
Percent
}
Next add a couple of functions that will perform the conversion of your enum to and from a string. With this in place you can add new members, and, as long as you add your attribute to them, the functions will automatically handle the new values.
public static string GetDescription(this Unit value)
{
FieldInfo field;
DescriptionAttribute attribute;
string result;
field = value.GetType().GetField(value.ToString());
attribute = (DescriptionAttribute)Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute));
result = attribute != null ? attribute.Description : string.Empty;
return result;
}
public static Unit GetValue(string value)
{
Unit result;
result = Unit.None;
foreach (Unit id in Enum.GetValues(typeof(Unit)))
{
FieldInfo field;
DescriptionAttribute attribute;
field = id.GetType().GetField(id.ToString());
attribute = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;
if (attribute != null && attribute.Description == value)
{
result = id;
break;
}
}
return result;
}
I choose to make the version that accepts the enum member as an input parameter an extension method, that way I can call it like this:
string unitSuffix;
unitSuffix = this.Unit.GetDescription();
However, as the sister method accepts a string parameter, it doesn't make sense to make this an extension, unless you want it to appear on every single string variable you declare! So I just revert back to the usual static calling convention.
Unit unit;
unit = EnumExtensions.GetValue(stringValue.Substring(nonDigitIndex));
Using Generics
While there's nothing wrong with the above methods, they could still be improved upon. As it stands now, the methods are fixed to a specific enum, so we can change them to use generics instead, then they'll work for all enums.
public static string GetDescription<T>(this T value)
where T : struct
{
FieldInfo field;
DescriptionAttribute attribute;
string result;
field = value.GetType().GetField(value.ToString());
attribute = (DescriptionAttribute)Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute));
result = attribute != null ? attribute.Description : string.Empty;
return result;
}
public static T GetValue<T>(string value, T defaultValue)
{
T result;
result = defaultValue;
foreach (T id in Enum.GetValues(typeof(T)))
{
FieldInfo field;
DescriptionAttribute attribute;
field = id.GetType().GetField(id.ToString());
attribute = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;
if (attribute != null && attribute.Description == value)
{
result = id;
break;
}
}
return result;
}
Final points
Using reflection does have an overhead. If you expect to be
calling these methods a lot, you may wish to extend them yet
further in order to support caching the results in a dictionary
or other mechanism of your choice. That way, the first time a
new member is requested you perform the reflection lookup, and
thereafter just read the cache. I haven't done any benchmarking,
but it's probably safe to say a dictionary lookup (remember to
use TryGetValue
!) is going to be a lot faster than a
reflection scan.
An example showing how the custom type converter from the previous two articles updated to use the above technique is available from the link below.
Update History
- 2013-07-28 - First published
- 2020-11-21 - Updated formatting
Downloads
Filename | Description | Version | Release Date | |
---|---|---|---|---|
CustomTypeConverter3.zip
|
Example project showing how to convert enum members to and from custom description strings. |
1.0.0.0 | 28/07/2013 | Download |
Leave a Comment
While we appreciate comments from our users, please follow our posting guidelines. Have you tried the Cyotek Forums for support from Cyotek and the community?
Comments
Ofer
#
Very nice and useful, thanks!