65.9K
CodeProject 正在变化。 阅读更多。
Home

强类型和高效的 .NET 枚举

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.50/5 (5投票s)

2009年5月4日

CPOL

1分钟阅读

viewsIcon

38042

downloadIcon

92

高效且强类型的 .NET 枚举类的替代方案

EfficientEnum.jpg

引言

在文章 使用 StrongEnum 进行更简洁的枚举编程 中,DevCubed 很好地展示了一个强类型的 Enum 类。这个类使使用 Enum 功能的代码更简单,但实现仍然基于内置的 Enum 类。Enum 类的一个缺点是它的性能,它基于反射查找。一些读者,包括我,都在寻找更高效的解决方案。

所需:通用、强类型、高效的枚举

我在这里想提出的解决方案是基于在初始化时(静态构造函数)调用内置的 Enum 来存储 enum 信息,然后保持它并在泛型字典(.NET 哈希表)中使用这些信息。

这里的性能提升在于初始化时间,以及存储字典的一些内存。这是这个类

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);
    }
}

使用代码 

附带的 C# 项目演示了 StrongQuickEnum 类的用法,并显示了使用 StrongQuickEnum 和内置 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());
    }
}

关注点

这个实现展示了一个想法,而不是一个完整的实现。例如,没有实现 Enum.Format 的替代方案。如果您实现任何扩展,请在评论中与我们分享!

历史

  • 2009年5月3日:初始发布
© . All rights reserved.