Click here to Skip to main content
15,881,882 members
Articles / Programming Languages / C#

Strong-Type & Efficient .NET Enum

Rate me:
Please Sign up or sign in to vote.
3.50/5 (5 votes)
4 May 2009CPOL1 min read 36.8K   81   15   12
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:

C#
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:

C#
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Generalgood article Pin
Donsw13-Jun-09 5:30
Donsw13-Jun-09 5:30 
GeneralDirect cast is faster. Pin
slyguy444-May-09 9:09
slyguy444-May-09 9:09 
GeneralRe: Direct cast is faster. Pin
Hana Giat4-May-09 15:23
Hana Giat4-May-09 15:23 
GeneralTake a look on EnumConverter : System.ComponentModel.TypeConverter Pin
Sergey Morenko4-May-09 5:15
professionalSergey Morenko4-May-09 5:15 
QuestionWhat does it do better ? Pin
KCorax24-May-09 5:02
KCorax24-May-09 5:02 
GeneralMy way Pin
PIEBALDconsult4-May-09 3:56
mvePIEBALDconsult4-May-09 3:56 
GeneralRe: My way Pin
Sergey Morenko4-May-09 5:10
professionalSergey Morenko4-May-09 5:10 
JokeRe: My way Pin
an_phu6-May-09 7:35
an_phu6-May-09 7:35 
GeneralRe: My way Pin
PIEBALDconsult6-May-09 7:38
mvePIEBALDconsult6-May-09 7:38 
Like what?
QuestionHow about _enumStrings.Black? Pin
loibl4-May-09 0:31
loibl4-May-09 0:31 
AnswerRe: How about _enumStrings.Black? Pin
Hana Giat4-May-09 15:31
Hana Giat4-May-09 15:31 
GeneralRe: How about _enumStrings.Black? Pin
slyguy448-May-09 11:11
slyguy448-May-09 11:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.