I was reading the source code of EqualityComparer<T>.Default
and found that it's not so clever. Here is an example:
enum MyEnum : int { A, B }
EqualityComparer<MyEnum>.Default.Equals(MyEnum.A, MyEnum.B)
//is as fast as
EqualityComparer<int>.Default.Equals(0, 1)
enum AnotherEnum : long { A = 1L, B = 2L }
//is 8x slower than
EqualityComparer<long>.Default.Equals(1L, 2L)
The reason is obvious from the source code of the private method in EqualityComparer.
private static EqualityComparer<T> CreateComparer()
{
//non-important codes are ignored
if (c.IsEnum && (Enum.GetUnderlyingType(c) == typeof(int)))
{
return (EqualityComparer<T>) RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType) typeof(EnumEqualityComparer<int>), c);
}
return new ObjectEqualityComparer<T>();
}
We can see EqualityComparer<int>.Default
,EqualityComparer<MyEnum>.Default
and EqualityComparer<long>.Default
get a wise comparer whose Equals
method looks like:
public static bool Equals(int x, int y)
{
return x == y; //or return x.Equals(y); here
//I'm not sure, but neither causes boxing
}
public static bool Equals(MyEnum x, MyEnum y)
{
return x == y; //it's impossible to use x.Equals(y) here
//because that causes boxing
}
The above two are clever, but EqualityComparer<AnotherEnum>.Default
is unlucky, from the method we can see at last it gets a ObjectEqualityComparer<T>()
, whose Equals
method probably looks like:
public static bool Equals(AnotherEnum x, AnotherEnum y)
{
return x.Equals(y); //too bad, the Equals method is from System.Object
//and it's not override, boxing here!
//that's why it's so slow
}
I think this condition Enum.GetUnderlyingType(c) == typeof(int)
is pointless, if the underlying type of an enum is of type int, the method can convert the default comparer of int to this enum. But why can't an enum based on long? It's not so hard i think? Any special reason? Constructing a comparer like x == y
isn't so hard for enum, right? Why at last it gives a slow ObjectEqualityComparer<T>
for enums(even it works correctly)?
I think that there's simply no compelling reason for the team responsible to add this feature. All features have an implementation cost which includes (among others) the time to document, code and test.
There are a couple of compelling reasons why this particular feature has not been picked over others so far (and will probably never make the cut IMO):
enums
backed by something other than anint
, and doing that in some inner loop)