问题在于
装箱。这是将值类型转换为对象的行为,可能是必要的,也可能是不必要的。
Dictionary
比较键的方式实质上是使用
EqualComparer<T>.Default
,并调用
GetHashCode()
来查找正确的桶,然后使用
Equals
比较是否有与我们正在查找的相等的值。
好消息是:.NET框架具有良好的优化,在
"Enum integers"
的情况下避免了装箱。请参见
CreateComparer()。你几乎不可能在这里看到任何差异,即整数和枚举作为键。
需要注意的是:这不是一个简单的行为,事实上,如果你深入挖掘,你会得出结论,这场战斗的四分之一是通过CLR“黑科技”实现的。如下所示:
static internal int UnsafeEnumCast<T>(T val) where T : struct
{
throw new InvalidOperationException();
}
如果泛型有枚举约束,甚至可能会像
UnsafeEnumCast<T>(T val) where T : Enum->Integer
这样的语句一样方便,但是...它们没有。您可能想知道,在
EnumCast
的getILIntrinsicImplementation中到底发生了什么?我也很好奇。目前还不确定如何检查它。我相信它会在运行时被具体的IL代码替换?!现在,回答您的问题:是的,你说得对。在紧密的循环中,使用
Enum
作为Mono上的键会更慢。因为就我所看到的而言,Mono对枚举进行了装箱。您可以查看
EnumIntEqualityComparer,正如您所看到的,它调用了
Array.UnsafeMov
,这基本上将类型
T
转换为整数,通过装箱:
(int)(object) instance;
。这是泛型的“经典”限制,对于这个问题没有好的解决方案。
解决方案1
为您的具体枚举实现一个EqualityComparer<MyEnum>
。这将避免所有的类型转换。
public struct MyEnumCOmparer : IEqualityComparer<MyEnum>
{
public bool Equals(MyEnum x, MyEnum y)
{
return x == y;
}
public int GetHashCode(MyEnum obj)
{
return (int)obj;
}
}
然后,您所需要做的就是将其传递给您的Dictionary
:
new Dictionary<MyEnum, int>(new MyEnumComparer());
这样可以正常工作,它可以提供与整数相同的性能,并避免装箱问题。但问题在于,这不是通用的,为每个Enum
编写此代码可能会感到愚蠢。
解决方案2
编写一个通用的Enum
比较器,并使用一些技巧来避免拆箱。我在这里得到了一点帮助。
struct FastEnumIntEqualityComparer<TEnum> : IEqualityComparer<TEnum>
where TEnum : struct
{
static class BoxAvoidance
{
static readonly Func<TEnum, int> _wrapper;
public static int ToInt(TEnum enu)
{
return _wrapper(enu);
}
static BoxAvoidance()
{
var p = Expression.Parameter(typeof(TEnum), null);
var c = Expression.ConvertChecked(p, typeof(int));
_wrapper = Expression.Lambda<Func<TEnum, int>>(c, p).Compile();
}
}
public bool Equals(TEnum firstEnum, TEnum secondEnum)
{
return BoxAvoidance.ToInt(firstEnum) ==
BoxAvoidance.ToInt(secondEnum);
}
public int GetHashCode(TEnum firstEnum)
{
return BoxAvoidance.ToInt(firstEnum);
}
}
解决方案3
现在,解决方案#2存在一个小问题,因为Expression.Compile()
在iOS上并不那么出名(没有运行时代码生成),而且一些mono版本也没有Expression.Compile
(不确定)。
您可以编写简单的IL代码来处理枚举转换,并将其编译。
.assembly extern mscorlib
{
.ver 0:0:0:0
}
.assembly 'enum2int'
{
.hash algorithm 0x00008004
.ver 0:0:0:0
}
.class public auto ansi beforefieldinit EnumInt32ToInt
extends [mscorlib]System.Object
{
.method public hidebysig static int32 Convert<valuetype
.ctor ([mscorlib]System.ValueType) TEnum>(!!TEnum 'value') cil managed
{
.maxstack 8
IL_0000: ldarg.0
IL_000b: ret
}
}
为了将其编译成程序集,您需要调用:
ilasm enum2int.il /dll
其中enum2int.il是包含IL代码的文本文件。
现在,您可以引用生成的程序集(
enum2int.dll
)并调用静态方法,如下所示:
struct FastEnumIntEqualityComparer<TEnum> : IEqualityComparer<TEnum>
where TEnum : struct
{
int ToInt(TEnum en)
{
return EnumInt32ToInt.Convert(en);
}
public bool Equals(TEnum firstEnum, TEnum secondEnum)
{
return ToInt(firstEnum) == ToInt(secondEnum);
}
public int GetHashCode(TEnum firstEnum)
{
return ToInt(firstEnum);
}
}
这段代码看起来很厉害,但它避免了装箱,并且应该在 Mono
上提供更好的性能。
Enum
作为字典键而不是整数。 - Servy