The title is pretty much clear I think.
I was wondering if there\'s a certain efficiency overhead when using IEqualityComparer
in a Dictionary<
Jonathan has a great answer that points out how, using the right equality comparer improves the performance and Jon clarifies in his great answer that Dictionary
always uses an IEqualityComparer
which is EqualityComparer
unless you specify another.
The thing I'd like to touch upon is the role of IEquatable
interface when you use the default equality comparer.
When you call the EqualityComparer
, it uses a cached comparer if there is one. If it's the first time you're using the default equality comparer for that type, it calls a method called CreateComparer
and caches the result for later use. Here is the trimmed and simplified implementation of CreateComparer
in .NET 4.5:
var t = (RuntimeType)typeof(T);
// If T is byte,
// return a ByteEqualityComparer.
// If T implements IEquatable,
if (typeof(IEquatable).IsAssignableFrom(t))
return (EqualityComparer)
RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter(
(RuntimeType)typeof(GenericEqualityComparer), t);
// If T is a Nullable where U implements IEquatable,
// return a NullableEqualityComparer
// If T is an int-based Enum,
// return an EnumEqualityComparer
// Otherwise return an ObjectEqualityComparer
But what does it mean for types that implement IEquatable
?
Here, the definition of GenericEqualityComparer
:
internal class GenericEqualityComparer : EqualityComparer
where T: IEquatable
// ...
The magic happens in the generic type constraint (where T : IEquatable
part) because using it does not involve boxing if T
is a value type, no casting like (IEquatable
is happening here, which is the primary benefit of generics.
So, let's say we want a dictionary that maps integers to strings.
What happens if we initialize one using the default constructor?
var dict = new Dictionary();
EqualityComparer.Default
unless we specify another.EqualityComparer.Default
will check if int implements IEquatable
.int
(Int32
) implements IEquatable
.First call to EqualityComparer
will create and cache a generic comparer which may take a little but when initialized, it's a strongly typed GenericEqualityComparer
and using it will cause no boxing or unnecessary overhead whatsoever.
And all the subsequent calls to EqualityComparer
will return the cached comparer, which means the overhead of initialization is one-time only for each type.
So what does it all mean?
T
does not implement IEquatable
or its implementation of IEquatable
does not do what you want it to do.obj1.Equals(obj2)
doesn`t give you the desired result.)Using of StringComparer
in Jonathan's answer is a great example why you would specify a custom equality comparer.
T
implements IEquatable
and the implementation of IEquatable
does what you want it to do.obj1.Equals(obj2)
gives you the desired result).In the latter case, use EqualityComparer
instead.