如何避免枚举通过Equals跟int类型做对比会装箱的问题。

为什么会产生装箱?

不管值类型还是枚举类型,它们的Equals的参数类型都是object。
image

image

微软的各种类型示例的比较的指导中提到:

对象比较值时用ReferenceEquals或者Equals;
值类型比较时直接利用=等号即可。
https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/equality-comparisons

.net和旧版的mono以enum作为字典key装箱的原因也是于此。

机制:
ContainsKey、TryGetValue或在字典中使用数组表示法查找字典中的项时,Dictionary将使用 EqualComparer.Default和调用 GetHashCode() 来找到正确的存储桶,对比器通过类型匹配会命中GenericEqualityComparer或者.net的EnumEqualityComparer,既会调用到Enum.Equals。

image

image

image

源码见:
https://github.com/dotnet/runtime/blob/1d1bf92fcf43aa6981804dc53c5174445069c9e4/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs#L818

image

新版的mono框架提供了对enum与int做对比的新的比较器EnumIntEqualityComparer:

image

它将通过写int数组的方式将枚举转为int类型后在做等号的对比。这样就避免的调用Equals的装箱。
image

源码见:
https://github.com/kitsilanosoftware/mono/blob/7008bb686727e4e18cafbbdb11ced4a463ddcbfb/mcs/class/corlib/System.Collections.Generic/EqualityComparer.cs#L186

(解决方案)如何避免枚举间或者枚举跟int做对比时,如何避免装箱?

我想答案已很浅显了。
就是不要直接使用Enum.Equals,而是先转为int再通过=等号做对比。
转换int可以利用如下代码:

public class EnumInt32ToInt
{
    public static int Convert<TEnum>(TEnum value) where TEnum : struct
    {
        return ArrayUtils.UnsafeMov<TEnum, int>(value);
    }
}

若采用了.net框架,以enum作为字典key。则可以自定义个比较器传入字典中。可以直接抄mono的EnumEqualityComparer(利用ArrayUtils.UnsafeMov转int方式)。
或者强行转换int类型的方式:

public struct MyEnumCOmparer : IEqualityComparer<MyEnum>
{
    public bool Equals(MyEnum x, MyEnum y)
    {
        return x == y;
    }

    public int GetHashCode(MyEnum obj)
    {
        // you need to do some thinking here,
        return (int)obj;
    }
}

通过Expression.ConvertChecked转为int的方式。

struct FastEnumIntEqualityComparer<TEnum> : IEqualityComparer<TEnum> 
    where TEnum : struct
{
    static class BoxAvoidance
    {
        static readonly Func<TEnum, int> _wrapper;

        public static int ToInt(TEnum enu) //(这里是使用一个c#编译为il库, 可以使用C# Union转化 或者 unsafe 直接使用指针)
        {
            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);
    }
}

通过il code的方式也行

.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
    }
}

参考:

《dictionary enum key how to avoid GC》
https://www.cnblogs.com/herenzhiming/articles/9245531.html
《EqualityComparar源码》
https://github.com/kitsilanosoftware/mono/blob/7008bb686727e4e18cafbbdb11ced4a463ddcbfb/mcs/class/corlib/System.Collections.Generic/EqualityComparer.cs#L186

posted @ 2025-06-29 03:28  昂流  阅读(21)  评论(0)    收藏  举报
//替换成自己路径的js文件 hhttp(s)://static.tctip.com/tctip-1.0.4.min.js