Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Strong-Type & Efficient .NET Enum

0.00/5 (No votes)
4 May 2009 2  
Efficient & strong-type alternative to the .NET Enum class
EfficientEnum.jpg

Introduction

In the article, Terser Enum Programming with StrongEnum, DevCubed does a great job presenting a strong-type Enum class. This class makes a piece of code using Enum functionality simpler, but the implementation is still of the built-in Enum class. The drawback of the Enum class is its performance, which is based on reflection lookups. Some of the readers, and me amongst them, looked for a more efficient solution. 

Wanted: Generic, Strong-Type, Efficient Enum

The solution I would like to suggest here is based on the idea of calling built-in Enum to store the enum info at initialization (static constructor), and from now on keep it and use the information in generics Dictionaries (.NET Hashtables).

The performance gain we have here is at initialization time, and some memory to hold the dictionaries. This is the class:

public class StrongQuickEnum<T>
{
    static StrongQuickEnum()
    {
        _t = typeof(T);
        string[] names = Enum.GetNames(_t);
        _strToEnum = new Dictionary<string, T>(names.Length);
        _strToEnumIgnoreCase = new Dictionary<string, T>(names.Length);
        _intToEnum = new Dictionary<int, T>(names.Length);
        foreach (string name in names)
        {
            T enumObject = (T)Enum.Parse(_t, name);
            _strToEnum.Add(name, enumObject);
            _strToEnumIgnoreCase.Add(name.ToLower(), enumObject);
            int enumInt = Convert.ToInt32(enumObject);
            _intToEnum.Add(enumInt, enumObject);
        }
    }
    /// <summary>
    /// Will serve us in the Parse method
    /// </summary>
    private static Dictionary<string, T> _strToEnum;
    /// <summary>
    /// Will serve us in the Parse method, with ignoreCase == true.
    /// It is possible not to hold this member and to define a Comparer class -
    /// But my main goal here is the performance at runtime.
    /// </summary>
    private static Dictionary<string, T> _strToEnumIgnoreCase;
    /// <summary>
    /// Will serve us in the ToObject method
    /// </summary>
    private static Dictionary<int, T> _intToEnum;
     private static Type _t;
     public static T Parse(string value)
    {
        return _strToEnum[value]; // Exception will be thrown if the value is not found
    }
    public static T Parse(string value, bool ignoreCase)
    {
        if (ignoreCase)
        {
            string valLower = value.ToLower();
            return _strToEnumIgnoreCase[valLower];
        }
        else
            return Parse(value);
    }
    public static T ToObject(object value)
    {
        try
        {
            int intval = (int)value;
            return ToObject(intval);
        }
        catch (InvalidCastException)
        {
            throw new ArgumentException("Cannot convert " + value + " to int");
        }
        //If an exception is coming from ToObject(intval), do not catch it here.
    }
     public static T ToObject(int value)
    {
        return _intToEnum[value];
    }
     public static string GetName(object value)
    {
        // We can hold an additional dictionary to map T -> string.
        // In my specific usage, this usages is rare, 
        // so I selected the lower performance option
        try
        {
            T valueT = (T)value;
            foreach (KeyValuePair<string, T> pair in _strToEnum)
            {
                int x = Convert.ToInt32(pair.Value);
                if (pair.Value.Equals(valueT))
                    return pair.Key;
            }
        }
        catch
        {
            throw new ArgumentException("Cannot convert " + value + 
						" to " + _t.ToString());
        }
        return null; // should never happen
    }
    public static string[] GetNames()
    {
        // .NET 3.5:
        // use the magic _strToEnum.Keys.ToArray() and that's it!
        string[] res = new string[_strToEnum.Count];
        int i = 0;
        foreach (string str in _strToEnum.Keys)
            res[i++] = str;
        return res;
    }
     public static Array GetValues()
    {
        Array res = Array.CreateInstance(_t, _strToEnum.Count);
        int i = 0;
        foreach (T enumObject in _strToEnum.Values)
            res.SetValue(enumObject, i++);
        return res;
    }
     public static bool IsDefined(object value)
    {
        try
        {
            int intval = (int)value;
            return _intToEnum.ContainsKey(intval);
        }
        catch
        {
            return false;
        }
    }
     public static Type GetUnderlyingType()
    {
        // Seems like this is good enough.
        return Enum.GetUnderlyingType(_t);
    }
}

Using the Code 

The attached C# project demonstrates the usage of the StrongQuickEnum class, and shows the timing difference between using StrongQuickEnum and the built-in Enum:

class Program
{
    enum Color
    {
        White,
        Black,
        Red,
        Yellow,
        Blue,
        Green,
        Cyan,
        Magenta,
        Pink,
        Purple,
        Orange,
        Brown
    }
     static string[] _enumStrings = new string[]
    {
        "White",
        "Black",
        "Red",
        "Yellow",
        "Blue",
        "Green",
        "Cyan",
        "Magenta",
        "Pink",
        "Purple",
        "Orange",
        "Brown"
    };
     const int _iterations = 100000;
     static void Main(string[] args)
    {
        Console.WriteLine("Number of iterations: " + _iterations);
        Random randomNumber = new Random();
        using(new PerformanceMonitor("{Built-in Enum class}"))
        {
            for(int i = 0; i<_iterations; i++)
            {
                int index = randomNumber.Next(0, 11);
                Color c1 = (Color)Enum.ToObject(typeof(Color), index);
                Color c2 = (Color)Enum.Parse(typeof(Color), _enumStrings[index]);
            }
        }
        // Verify initialization of the data out of the comparative measurement.
        // As you can see, this initialization is the gain for the later efficiency.
        Color init = StrongQuickEnum<Color>.ToObject(2);
        using (new PerformanceMonitor("{StrongQuickEnum<Color> class}"))
        {
            for(int i = 0; i<_iterations; i++)
            {
                int index = randomNumber.Next(0, 11);
                Color c1 = StrongQuickEnum<Color>.ToObject(index);
                Color c2 = StrongQuickEnum<Color>.Parse(_enumStrings[index]);
            }
        }
        Console.ReadLine();
    }
}
 class PerformanceMonitor : IDisposable
{
    long _timestarted;
    string _name;
     internal PerformanceMonitor(string name)
    {
        _name = name;
        _timestarted = DateTime.Now.Ticks;
    }
     public void Dispose()
    {
        Console.WriteLine("Operation " + _name + ":\t\t" + 
			(DateTime.Now.Ticks - _timestarted).ToString());
    }
}

Points of Interest

This implementation demonstrates an idea, and not a full implementation. For example, an alternative to Enum.Format is not implemented. If you implement any extension, please share it with us as a comment!

History

  • 3rd May, 2009: Initial post

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here