It's said that "the devil is in the details". Well I saw some code the other day that reminded me exactly that quote. It is quite common for developers to forget about semantics. In my current project we tend to play a lot with Nhibernate and DDD and we seriously take care of our model. We take it to the extreme where we use every possible aspect defined in DDD, with Components being on of them. It is quite crucial to be careful when dealing with Components especially if ones uses them in a Set. Sets (as know from high-school math) are structures that can have only one instance per object. So we need to be sure that two objects are equal when we try to insert something to our Set (and respectively to the DB.)
I will try to show here the complete implementation of a class that needs to override Equals() as it should be. First we have to remember that in C# the semantics of equality define that two reference-objects are equal if the respective references point to the same object (aka object identity). Well, some people tend to forget that...
Here is how one should implement an object that needs to define its own version of Equals.
public class Foo : IEquatable<Foo>{public override bool Equals(Object right) {// check null:// this pointer is never null in C# methods.if (Object.ReferenceEquals(right, null))return false;if (Object.ReferenceEquals(this, right))return true;if (this.GetType() != right.GetType())return false;return this.Equals(right as Foo);}#region IEquatable<Foo> Memberspublic bool Equals(Foo other) {// Do your magic here...return true;}#endregion}
So what is going here? First things first...
Remember that .NET defines two versions of Equals:
public static Boolean Equals(Object left, Object right);
public virtual Boolean Equals(Object right);
Why don't we just use the static method Object.Equals? Well this method will do it's job only if both instances (this and right) are of the same type. That is because the static Equals all it does is a nullability check of both ends and then delegates the equality decision to the instance method Equals method. This is why we actually need to override the instance method and we NEVER touch the static method.
Another thing to note is that Equals should never throw any exception. It just doesn't make much sense. Two variables are or are not equal; there’s not much room for other failures. Just return false for all failure conditions, such as null references or the wrong argument types. Now, let’s go through this method in detail so you understand why each check is there and why some checks can be left out. The first check determines whether the right-side object is null. There is no check on this reference. In C#, this is never null. The CLR throws an exception before calling any instance method through a null reference. The next check determines whether the two object references are the same, testing object identity. It’s a very efficient test, and equal object identity guarantees equal contents. The next check determines whether the two objects being compared are the same type. The exact form is important. First, notice that it does not assume that this is of type Foo; it calls this.GetType(). The actual type might be a class derived from Foo. Second, the code checks the exact type of objects being compared. It is not enough to ensure that you can convert the
right-side parameter to the current type (inheritance bugs can surface if you go this way).
Finally we delegate the decision to the type-safe implementation of Equals that is coming from the IEquatable interface. Overriding Equals() means that your type should implement IEquatable<T>. IEquatable<T> contains one method: Equals(T other). Implemented IEquatable<T> means that your type also supports a type-safe equality comparison. If you consider that the Equals() should return true only in the case where the right-hand side of the equation is of the same type as the left side, IEquatable<T> simply lets the compiler catch numerous occasions where the two objects would be not equal. There is another practice to follow when you override Equals(). You should call the base class only if the base version is not provided by System.Object or System.ValueType.
There are a few other rules that apply to value types but they are beyond the scope of this post which is already huge I think. One last not though just to make sure we all understand where all these semantics and practices come from. Math 101, the base of all our computer lives:
Equality is reflexive, symmetric, and transitive. The reflexive property means that any object is equal to itself. No matter what type is involved, a == a is always true. The symmetric property means that order does not matter: If a == b is true, b == a is also true. If a == b is false, b == a is also false. The last property is that if a == b and b == c are both true, then a == c must also be true. That’s the transitive property. This is common knowledge to every developer, so please try to meet their expectations (even the simpler ones) whenever you handle some code to them ;)
No comments:
Post a Comment