Tuesday, January 10, 2012

Descriptive Text for Enums

While working on an windows application, I came to across an interesting problem, which is of trivial in nature. I had a drop-down (combo box) control which was populated with field names from an Enum, which is acting as a container for designation values. The problem with Enums were, the field names can't contains spaces, where as the actual designations did contain spaces. This made the end users feel a bit odd, the way which values were listed in the drop-down control. So putting some thoughts on this, I came up with a solution, which did hit the bulls eye. So here's how I did it.


First I need a container to hold this descriptive text, for this an attribute class called "EnumDescription" was defined to hold the string
[AttributeUsage(AttributeTargets.Field)]
public class EnumDescription : Attribute
{
    public EnumDescription(string Value)
    {
        if (!string.IsNullOrEmpty(Value))
            Description = Value;
        else
            throw new NullReferenceException("EnumDescription not provided");
    }

    public string Description { get; set; }
}

Next I started decorating the enum fields with the previously created attribute class "EnumDescription"; this allowed me to pass descriptive text to the enum fields, which helped me get past the .NET enum field naming contsraint.
enum Designations
{
 [EnumDescription("General Manager")]
 GeneralManager = 1,

 [EnumDescription("Project Manager")]
 ProjectManager,

 [EnumDescription("Technical Manager")]
 TechnicalManager,

 [EnumDescription("Group Project Manager")]
 GroupProjectManager,

 [EnumDescription("QA Manager")]
 QAManager,

 [EnumDescription("Accounts Manager")]
 AccountsManager,

 Designer
}
Next step of the puzzle is to read the string contained in the custom attributes. For this a simple custom attributes reader was implemented, but when I ran a unit test against the code snippet, it failed. The reason being, some enum fields doesn't require custom attributes as the designation names they represent didn't contain spaces. The code snippet I had written, now need to deal with enum fields with and without custom attributes, i.e; the catch here is to return the value contained in the EnumDescription, if the attribute is defined, else the field name itself should be returned.

public static string GetText(Enum e)
{
 string ReturnVal = string.Empty;
 EnumDescription oEnumDescription = null;

 if (e != null)
 {
  FieldInfo oFieldInfo = null;
  oFieldInfo = e.GetType().GetField(e.ToString());

  if (oFieldInfo != null)
  {
   object[] CustomAttribs = oFieldInfo.GetCustomAttributes(typeof(EnumDescription), false);

   if (CustomAttribs != null && CustomAttribs.Length >= 1)
   {
    oEnumDescription = (EnumDescription)CustomAttribs[0];
   }
  }

  if (oEnumDescription != null)
   ReturnVal = oEnumDescription.Description;
  else
   ReturnVal = e.ToString();
 }

 return ReturnVal;
}


Now the problem of getting descriptive text is sorted out. The next piece of the puzzle is how to set enum value based on the custom attribute text value? for this a SetText method was written, which will accept the value to be set, either based on the description or on the field name.

public static T SetText<T>(string Value) where T : struct
{
 T ReturnVal = default(T);
 EnumDescription oEnumDescription = null;
 bool DescriptionFound = false;

 if (!string.IsNullOrEmpty(Value))
 {
  //iterate through all the enum fields and check for the given Value in the EnumDescription
  foreach (FieldInfo fld in typeof(T).GetFields())
  {
   object[] CustomAttribs = fld.GetCustomAttributes(typeof(EnumDescription), false);

   if (CustomAttribs != null && CustomAttribs.Length >= 1)
   {
    oEnumDescription = (EnumDescription)CustomAttribs[0];

    if (oEnumDescription != null &&
     oEnumDescription.Description.ToLower() == Value.ToLower())
    {
     DescriptionFound = true;
     ReturnVal = (T)Enum.Parse(typeof(T), fld.Name);
     break; //we found the matching field so exit the foreach loop
    }
   }
  }

  //If no attribute with the given text was found, look for field names
  if (!DescriptionFound)
  {
   foreach (FieldInfo fld in typeof(T).GetFields())
   {
    if (fld != null &&
     fld.Name.ToLower() == Value.ToLower())
    {
     DescriptionFound = true;
     ReturnVal = (T)Enum.Parse(typeof(T), fld.Name);
     break; //we found the matching field so exit the foreach loop
    }
   }
  }
    
 }

 if (!DescriptionFound)
  throw new ApplicationException("Invalid description or field name: " + Value);

 return ReturnVal;
}

with these two methods in place I can now access and assign Enum descriptive text to Enums.
Here's a the code snippet which I used to test my two methods.
class Program
{
 static void Main(string[] args)
 {
  Designations SelectedDesig;
  string Value;

  SelectedDesig = Designations.GeneralManager;
  Value = EnumStringReader.GetText(SelectedDesig);
  Console.WriteLine("GetDescription: " + Value);

  SelectedDesig = EnumStringReader.SetText<Designations>("Designer");
  Console.WriteLine("SetEnum from Description: " + SelectedDesig);
 }
}
You can download the sample C# project from this link.