在Dictionary中使用枚举

简介:

自从.NET Framework 2.0引入泛型之后,对集合的使用就开创了新的局面。首先我们不用考虑类型是否安全,利用泛型以及对泛型参数的约束完全可以保障这一点;其次,集合元素不会因为频繁的Boxing和Unboxing而影响集合遍历与操作的性能。泛型带来的这两点好处毋庸置疑。在Dictionary<TKey, TValue>中,除了字符串,我们普遍会使用值类型作为它的key,例如int类型。而枚举类型作为一种值类型,在某些时候特别是需要位操作的时候,也会经常用作key。问题就出现在这里。

我们知道,Dictionary的key必须是唯一的标识,因此Dictionary需要对key进行判等的操作,如果key的类型没有实现 IEquatable接口,则默认根据System.Object.Equals()和GetHashCode()方法判断值是否相等。我们可以看看常用作key的几种类型在.NET Framework中的定义:

public  sealed  class  String : IComparable, ICloneable, IConvertible,   
    IComparable< string>, IEnumerable< string>, IEnumerable,   
    IEquatable< string>

public  struct  Int32 : IComparable, IFormattable,   
    IConvertible, IComparable< int>, IEquatable< int>

public  abstract  class  Enum : ValueType,   
    IComparable, IFormattable, IConvertible

注意Enum类型的定义与前两种类型的不同,它并没有实现IEquatable接口。因此,当我们使用Enum类型作为key值时,Dictionary的内部操作就需要将Enum类型转换为System.Object,这就导致了Boxing的产生。没错,我们很难发现这个陷阱,它是导致Enum作为 key值的性能瓶颈。

我们该如何解决这一问题?最简单的方法是将Enum的值先转换为int,然后将其作为key传入Dictionary中。还有一种作法是定义一个实现了IEqualityComparer<T>接口的类。因为Dictionary构造函数的其中一个重载版本,可以接收 IEqualityComparer<T>类型,通过它完成对key的判断。IEqualityComparer<T>接口的定义如下所示:

public  interface IEqualityComparer<T>
{      
     bool Equals(T x, T y);       
     int GetHashCode(T obj);
}

遗憾的是我们却不能直接提供针对Enum的实现,例如:

class  EnumComparer<TEnum> : IEqualityComparer<TEnum>
{
     public  bool Equals(TEnum x, TEnum y)
     {      
         return (x == y);
     }
     public  int GetHashCode(TEnum obj)
     {       
         return ( int)obj;
     }
}

因为我们不能直接对泛型类型进行==操作,以及将泛型对象强制转换为int类型。在Code Project上,有一篇名为Accelerating Enum-Based Dictionaries with Generic EnumComparer的文章,利用Reflection.Emit实现Equals()和GetHashCode()方法。不过在该文的评论中,提供了更好的一个方法,就是利用C# 3.0的Lambda表达式:

public  class  EnumComparer<T> : IEqualityComparer<T> where T :  struct
{
     public  bool Equals(T first, T second)
     {
        var firstParam = Expression.Parameter( typeof(T),  "first");
        var secondParam = Expression.Parameter( typeof(T),  "second");
        var equalExpression = Expression.Equal(firstParam, secondParam);

         return Expression.Lambda<Func<T, T,  bool>>
            (equalExpression,  new[]  { firstParam, secondParam  }).
            Compile().Invoke(first, second);
     }

     public  int GetHashCode(T instance)
     {
        var parameter = Expression.Parameter( typeof(T),  "instance");
        var convertExpression = Expression.Convert(parameter,  typeof( int));

         return Expression.Lambda<Func<T,  int>>
            (convertExpression,  new[] {parameter }).
            Compile().Invoke(instance);
     }
}

此时,我们就可以如此使用Dictionary对象:

public  enum DayOfWeek { //...}
var dictionary =  new Dictionary<DayOfWeek,  int>( new EnumComparer<DayOfWeek>());

采取这样的做法比直接用Enum类型作为Dictionary的key差不多要快8倍。这难道不让人为之惊诧吗?






本文转自wayfarer51CTO博客,原文链接:http://blog.51cto.com/wayfarer/280088,如需转载请自行联系原作者

相关文章
|
7月前
|
C#
C#中字典Dictionary的用法详解
C#中字典Dictionary的用法详解
|
7月前
|
C#
C#由Dictionary赋值引发的对引用类型使用的思考
C#由Dictionary赋值引发的对引用类型使用的思考
|
存储
Object C学习笔记13-Dictionary字典
  通过Array数组和Set集合的学习和理解,可以想象得到Dictionary也分为两种情况了,那就是可变和不可变两种类型的。的确如此,在Object C中提供了两个字典类,分别为NSDictionary 和 NSMutableDictionary. 在.NET中我们也学习过Dictionary类,这个集合的存储方式是键值对的方式存储的。
879 0
|
测试技术
Object C学习笔记19-枚举
  一. 枚举类型     枚举类型是一个基本类型,不能再分为为任何其他的类型。在一般的编程语言中都有枚举(enum)这种数据结构类型。枚举类型主要用于将一个变量限定在特定的范围内。比如一周有七天,那么一周的值就限定在了七个值内。
949 0
|
算法 C#
C# 字典 Dictionary<Tkey,Tvalue>
最近悟出来一个道理,在这儿分享给大家:学历代表你的过去,能力代表你的现在,学习代表你的将来。我们都知道计算机技术发展日新月异,速度惊人的快,你我稍不留神,就会被慢慢淘汰!因此:每日不间断的学习是避免被淘汰的不二法宝。
1312 0
|
图形学
对复杂字典Dictionary<T1,T2>排序问题
原文:对复杂字典Dictionary排序问题 //VoltageCount类(电压值对应的数量):    public class VoltageCount    {        public Double Vol...
805 0