在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 abstractclass Enum : ValueType, IComparable, IFormattable, IConvertible
注意Enum型別的定義與前兩種型別的不同,它並沒有實現IEquatable介面。因此,當我們使用Enum型別作為key值時,Dictionary的內部操作就需要將Enum型別轉換為System.Object,這就導致了Boxing的產生。沒錯,我們很難發現這個陷阱,它是導致Enum作為
key值的效能瓶頸。
我們該如何解決這一問題?最簡單的方法是將Enum的值先轉換為int,然後將其作為key傳入 Dictionary中。還有一種作法是定義一個實現了IEqualityComparer<T>介面的類。因為Dictionary
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倍。這難道不讓人為之驚詫