Story of Equality in .Net - Part 3
Introduction
After reading,
Story of Equality in .Net - Part 1
Story of Equality in .Net - Part 2
you can see that Object.Equals method has two problems. One thing is that it lacks strong typing and for value types boxing needs to be done. In this post we will look at the IEquatable<T> interface which provides solution to these problems.
Story of Equality in .Net - Part 1
Story of Equality in .Net - Part 2
you can see that Object.Equals method has two problems. One thing is that it lacks strong typing and for value types boxing needs to be done. In this post we will look at the IEquatable<T> interface which provides solution to these problems.
IEquatable<T> Interface
The generic IEquatable <T> exists for solving a
slightly different problem with Equals method. The Equals method on Object type
takes parameter of type Object. We know that this is the only type of parameter
which is possible if we want Object.Equals to work for all types.
But Object is a reference type which means that if you want
to pace a value type as an argument, the value type would be boxed which will
be a performance hit which is bad. Typically when we define value type instead
of reference type is because we are concerned of performance, so we always want
to avoid this performance overhead of boxing and unboxing.
There is also another problem, having an object as parameter
means that there is no type safety. For Example, the following code will
compile without any problem:
class Program { static void Main(String[] args) { Person p1 = new Person("Ehsan Sajjad"); Program p = new Program(); Console.WriteLine(p1.Equals(p)); Console.ReadKey(); } }
There is nothing to stop me for calling Equals method on two
difference type of instances. We are comparing instance of Person class with
instance of Program and compiler didn’t stopped me doing that, which is clearly
an issue as both are totally different types and there is no way they can
meaningfully equal each other.
We have three integer variables which we are comparing using Equals and printing the result on the console. If we look at the intellisense, we can see that there are two Equals method for int, one of them takes object as parameter and that’s the overridden Object.Equals method, other one takes an integer as parameter, this Equals method is implementation of IEquatable<int> by integer type, and this is the overload which will be used for comparison of the above example code, because in both Equals call we are passing integer as parameter not object, so the compiler will pick the overload defined for IEquatable<int> as it is the best signature match.
This is obviously very unnatural way to compare integers, normally we just write like:
We have written the code via Equals method so that you can see that there are two Equals method out there. All primitive supports provide the implementation for IEquatable<T> interface. Just take the above example, int implements the IEquatable<int>.
But it is worth noting here that String which is
a reference type does implements IEquatable<T>. If you recall from the Part – 2, when we were
demonstrating the Equals method for string we were explicitly casting the
string variable to object.
This was just an example, you should not be doing this kind
of comparisons in your code, and obviously it would be nice if compiler could
pick up this kind of situation, right now it cannot because Object.Equals
method does not have strong type safety.
We can solve this boxing and type safety issue by having an
Equals method that takes the type being compare as parameter, so for example we
can have an Equals method on String which takes a string as parameter and we can have an Equals method on Person class
which takes a Person variable as parameter. This will solve both boxing and
type safety problem nicely.
As we talked in the previous post about the problem of
inheritance with the above approach. But there is no way to usefully define
these strongly typed methods on System.Object,
because System.Object does not know
what types will be deriving from it.
So how can we make a strongly typed Equals method generally
available to consume. Microsoft solved this problem by providing the interface IEquatable<T> which can be
exposed by any type that wants to provide strongly typed Equals method. If we
look at the documentation we can see that IEquatable<T> exposes just one
method called Equals which returns a bool.
This serves exactly
the same purpose as Object.Equals, but
it takes the generic type T instance as a parameter and therefore it is
strongly typed which means for value types there will be no boxing to be done.
IEquatable<T> and Value Types
We can illustrate the IEquatable<T> interface with one
of the simplest type integer.
static void Main(String[] args) { int num1 = 5; int num2 = 6; int num3 = 5; Console.WriteLine(num1.Equals(num2)); Console.WriteLine(num1.Equals(num3)); }
We have three integer variables which we are comparing using Equals and printing the result on the console. If we look at the intellisense, we can see that there are two Equals method for int, one of them takes object as parameter and that’s the overridden Object.Equals method, other one takes an integer as parameter, this Equals method is implementation of IEquatable<int> by integer type, and this is the overload which will be used for comparison of the above example code, because in both Equals call we are passing integer as parameter not object, so the compiler will pick the overload defined for IEquatable<int> as it is the best signature match.
This is obviously very unnatural way to compare integers, normally we just write like:
Console.WriteLine(num1 == num2);
We have written the code via Equals method so that you can see that there are two Equals method out there. All primitive supports provide the implementation for IEquatable<T> interface. Just take the above example, int implements the IEquatable<int>.
Likewise other primitive types also implement
IEquatable<T>. Generally IEquatable<T> is very useful for value
types. Unfortunately Microsoft had not been very consistent about implementing
it for non-primitive value types in the Framework Class Library, so you can’t
always rely on this interface to be available
IEquatable<T> and Reference Types
.IEquatable<T> is not that much useful for reference
types as it is for value types. Because for reference types there is not really
any performance issues like we had for value types (boxing) which needs fixing
and also for the reason that IEquatable<T> does not play nicely with
inheritance.
static void Main(String[] args) { string s1 = "Ehsan Sajjad"; string s2 = string.Copy(s1); Console.WriteLine(s1.Equals((object)s2)); }
That was to make sure that it call the Object.Equals
override which takes object as parameter, if we don’t do that then compiler
will pick the strongly typed Equals method and that method is actually the
implementation of IEquatable<string> implemented by String . String is a
sealed class so you cannot inherit from it, so the issue of conflict between
Equality and Inheritance does not arise.
Obviously you would expect that when both Equals method are
available on a type, the virtual Object.Equals method and the
IEquatable<T> Equals method, they should always give the same result.
That’s’ true for all the Microsoft implementations and it’s one of the things
that is expected of you when you implement this interface yourself.
If you want to implement IEquatable<T> interface, then
you should make sure that you override the Object.Equals method to do exactly
the same thing as your interface method does and that makes sense, because it
should be clear that if a type implements two versions of Equals that behave
differently, then developers who will consume your type will get very confused.
Summary
·
We saw that we can implement IEquatable<T>
on our types to provide a strongly typed Equals method which also avoids boxing
for value types.
IEquatable<T> is implemented for primitive numeric types but unfortunately
Microsoft has not been very proactive
implementing for other value types in the Framework Class Library
1 comments:
Write commentsGreat share
Reply