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);
}
}
private static Dictionary<string, T> _strToEnum;
private static Dictionary<string, T> _strToEnumIgnoreCase;
private static Dictionary<int, T> _intToEnum;
private static Type _t;
public static T Parse(string value)
{
return _strToEnum[value];
}
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");
}
}
public static T ToObject(int value)
{
return _intToEnum[value];
}
public static string GetName(object value)
{
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;
}
public static string[] GetNames()
{
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()
{
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]);
}
}
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