Click here to Skip to main content
Click here to Skip to main content

Strong-Type & Efficient .NET Enum

, 4 May 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Hana Giat
Software Developer
United States United States
No Biography provided

Comments and Discussions

 
Generalgood article PinmemberDonsw13-Jun-09 6:30 
GeneralDirect cast is faster. Pinmemberslyguy444-May-09 10:09 
GeneralRe: Direct cast is faster. PinmemberHana Giat4-May-09 16:23 
GeneralTake a look on EnumConverter : System.ComponentModel.TypeConverter PinmemberGSerjo4-May-09 6:15 
QuestionWhat does it do better ? PinmemberKCorax24-May-09 6:02 
GeneralMy way PinmemberPIEBALDconsult4-May-09 4:56 
GeneralRe: My way PinmemberGSerjo4-May-09 6:10 
JokeRe: My way Pinmemberan_phu6-May-09 8:35 
GeneralRe: My way PinmemberPIEBALDconsult6-May-09 8:38 
QuestionHow about _enumStrings.Black? Pinmemberloibl4-May-09 1:31 
AnswerRe: How about _enumStrings.Black? PinmemberHana Giat4-May-09 16:31 
GeneralRe: How about _enumStrings.Black? Pinmemberslyguy448-May-09 12:11 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150224.1 | Last Updated 4 May 2009
Article Copyright 2009 by Hana Giat
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid