8

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)?

Cheng Chen
  • 42,509
  • 16
  • 113
  • 174
  • as a side note it is possible to write your own which does the right thing. But I haven't seen a way to do it without either code generation or C++/CLI. I couldn't find a way, even with unsafe{} to allow reinterpret casts via void* in c# with generic types. – ShuggyCoUk May 01 '11 at 17:31
  • 1
    I just published a small library on GitHub that lets you make the default equality comparer as clever as you want: https://github.com/smartcaveman/undefault – smartcaveman Apr 29 '13 at 18:34

1 Answers1

7

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):

  • It only applies to a very narrow scenario (comparing enums backed by something other than an int, and doing that in some inner loop)
  • There is a very straightforward and discoverable solution if it causes you a problem (write your own comparer)
Jon
  • 428,835
  • 81
  • 738
  • 806
  • Yes, I think the `narrowness` of the scenario is the point. If they had considered long, they should have included byte, short etc. too... – digEmAll Apr 29 '11 at 08:59
  • But still, if they took the effort to add the condition, it must had been for a reason. – František Žiačik Apr 29 '11 at 09:06
  • 1
    @FrantišekŽiačik: Covering the 99% case would be a reason :) – Jon Apr 29 '11 at 09:23
  • 1
    I didn't make myself clear. If I cite Danny Chen, *"I think this condition `Enum.GetUnderlyingType(c) == typeof(int)` is pointless*, if it's true, then adding it would narrow the coverage from 100% down to 99%. So they should have a good reason for this, shouldn't they. – František Žiačik Apr 29 '11 at 09:27
  • 1
    Ah, I looked at the code again and now I see, they had to use a concrete `EnumEqualityComparer<>`, that's the reason. – František Žiačik Apr 29 '11 at 09:29