枚舉類型作為一種值類型,在某些時候特别是需要位操作的時候,也會經常用作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>());