在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倍。这难道不让人为之惊诧
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值