How to Enumerate Strings using Resource Files in C#

Strings are not valid types within enumerations. So the coder usually has to create a whole bunch of constant string values and simply have them grouped together within their classes. It’s not very desirable for a few reasons. For starters, there is no enforcement that a string value has to belong to a particular class, and also, they are much harder to read in from resource files.

Enumerations are an excellent way to make code more reliable, readable and modifiable. They are a special case, where every value of a variable is already known, and you want to be able to describe them in a more readable way. They allow each value to be grouped together so that there is a reduced likelihood that an incorrect value is specified in code.

For example,

public enum Flowers
{
   Rose,
   Tulip,
   Daisy,
   Daffodil
}

If you declare a variable of type Flowers, then you know that only Rose, Tulip, Daisy, Daffodil can be selected.

To do the equivalent with strings would require, perhaps, a class

public class Vehicles
{
    public const string AlfaRomeo = "Alfa Romeo";
    public const string MercedesBenz = "Mercedes Benz";
    public const string Ford = "Ford";
    public const string Porche = "Porche";
    public const string AstonMartin = "Aston Martin";
}

But there is nothing to prevent you from using a string from the wrong class in your code. If you declare a variable for the car

string myCar;

There is nothing to prevent setting the value to something other than what exists in the class Cars.

myCar = "Honda";

Another major benefit of enumeration is that non-domain errors are caught at compile time, not runtime. Finding errors at compile time is significantly cheaper to resolve than finding the error after it has gone live. So it is highly desirable to find these sorts of errors as early as possible in the process.

And it can be done!

I will demonstrate how to better group your strings, and also to treat those strings as enumerations. Here, I will use attributes and reflection to retrieve the values from resource files.

So to start with, create a new string resources file. I have called mine MessageResources. Add a few strings to the resource file. I have added mine as follows:

Message Resources File containing Vehicle strings

Note that I have deliberately chosen names that include spaces. When you use the ToString() method on a standard enumeration, expecting to see their values, it is significantly harder to use those values in, say, a combo box, if they don’t have spaces in them.

Now let me skip to the enumeration itself.

Start with a typical enumeration:

public enum Vehicles
{
    AlfaRomeo,
    MercedesBenz,
    Ford,
    Porche,
    AstonMartin
}

Of course, you need to link this to the resource file, so we’ll do that via attributes. So I will create an attribute for the Enumeration, as follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace EnumHelperSample
{
    public class EnumAttribute : Attribute
    {
        public Type ResourceType { get; private set; }
        public string ResourceName { get; private set; }
        public int SortOrder { get; private set; }

        public EnumAttribute(Type ResourceType,
                             string ResourceName,
                             int SortOrder)
        {
            this.ResourceType = ResourceType;
            this.ResourceName = ResourceName;
            this.SortOrder = SortOrder;
        }
    }
}

I have decided to include the type of the resource file that contains the strings, the name of the string resource, and the order that I would like the enumerations to be sorted if returned as a list of strings.

However, this won’t work. If you have a look in the MessageResources.Designer.cs file, this generates the following code:


/// <summary>
///   Looks up a localized string similar to Alfa Romeo.
/// </summary>
internal static string AlfaRomeo {
   get {
       return ResourceManager
              .GetString("AlfaRomeo", resourceCulture);
   }
}

For a value to be passed via an attribute, it needs to be a constant, and the MessageResources generated code does not provide a constant string.

Enter Dmytro Kryvko and the ResXFileCodeGeneratorEx Custom Tool.

Dmytro has replaced the custom tool found on the standard Resources file with a new custom tool that he wrote. This tool generates a constant string for every resource name that is generated within the resource file.

You can download it from here:
http://www.codeproject.com/KB/dotnet/ResXFileCodeGeneratorEx.aspx

If you really want to go and improve his code, by all means, go ahead. For our purposes, it will be fine simply to run the installer.

After installing this on your machine, you select properties on the resources file, and change the custom tool to the extended version, ending in Ex.

Changing Message Resources Custom Tool to ResXFileCodeGeneratorEx

Now we can pass the resource names as constants.

The enumeration code then becomes:

namespace EnumHelperSample
{
    public enum Vehicles
    {
        [EnumAttribute(typeof(MessageResources),
                       MessageResources.ResourceNames.AlfaRomeo,
                       1)]
        AlfaRomeo,
        [EnumAttribute(typeof(MessageResources),
                       MessageResources.ResourceNames.MercedesBenz,
                       4)]
        MercedesBenz,
        [EnumAttribute(typeof(MessageResources),
                       MessageResources.ResourceNames.Ford,
                       3)]
        Ford,
        [EnumAttribute(typeof(MessageResources),
                       MessageResources.ResourceNames.Porche,
                       5)]
        Porche,
        [EnumAttribute(typeof(MessageResources),
                       MessageResources.ResourceNames.AstonMartin,
                       2)]
        AstonMartin
    }

}

Ok, that’s all very well, but how do we retrieve the values?

Well, I have written an EnumHelper class as a set of extensions to retrieve those values.

This first one is to retrieve a value from the associated resource. I do this by extending the Enum type. It allows me to write the following syntax:

Debug.WriteLine(Vehicles.AlfaRomeo.GetString());
Debug.WriteLine(Vehicles.MercedesBenz.GetString());

This outputs:

Alfa Romeo
Mercedes Benz

public static string GetString(this Enum value)
{
    EnumAttribute ea =
               (EnumAttribute)value.GetType()
                .GetField(value.ToString())
                .GetCustomAttributes(typeof(EnumAttribute), false)
                .FirstOrDefault();
     if (ea != null)
     {
           PropertyInfo pi = ea.ResourceType
                               .GetProperty("ResourceManager");
            if (pi != null)
            {
                ResourceManager rm = (ResourceManager)pi
                                     .GetValue(null, null);
                return rm.GetString(ea.ResourceName);
            }
     }

     return string.Empty;
}

And finally to retrieve the entire sorted list, I extended Type itself, as follows:

public static IList GetStrings(this Type enumType)
{
    SortedList<int, string> stringList = new SortedList<int, string>();
    FieldInfo[] fiArray = enumType.GetFields();
    foreach (FieldInfo fi in fiArray)
    {
        EnumAttribute ea =
            (EnumAttribute)fi
                 .GetCustomAttributes(typeof(EnumAttribute), false)
                 .FirstOrDefault();
         if (ea != null)
         {
              PropertyInfo pi = ea.ResourceType
                                  .GetProperty("ResourceManager");
              if (pi != null)
              {
                  ResourceManager rm = (ResourceManager)pi
                                        .GetValue(null, null);
                  stringList.Add(ea.SortOrder,
                                 rm.GetString(ea.ResourceName));
              }
        }
    }
    return stringList.ToList();
}

So to display the entire sorted list of values:

IList result = typeof(Vehicles).GetStrings();
foreach (KeyValuePair<int,string> kvp in result)
{
    Debug.WriteLine(kvp.Value);
}

Finally, a note. Allowing the enumerations to be specified via Resource Name constants passed as attributes still requires the use of strings. This means other string constants could be passed in to the attribute types.

I believe that the likelihood of this occurring is significantly less of a risk than if the developer was loading up and passing around string constants themselves. And anyway, you’ll need to know which resource file that the strings are coming from. If you are that worried about it, you could always modify Dmytro’s code!

Meanwhile, my code may be found here:

EnumHelperSample.zip

3 Responses to How to Enumerate Strings using Resource Files in C#

  1. markhurd says:

    In your examples, because you’re dealing with one Attribute type, you can use the GetCustomAttributes overload that specifies the type you’re interested in.

  2. […] This can be used to get my original code working under Visual Studio 2010 from the article: How to Enumerate Strings using Resource Files in C# […]

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: