I am also going to use unit tests to prove/disprove what we think is going on.
We think of things that are equal contain the same values - or have something within them to make them equal. However, equality (for reference types) generally by default means that it is the same object (the string class is different - two strings are the same if they have the same value, but although strings are a reference type they generally work like a value type).
Firstly let's create a class definition for an employee that has two fields - the employee's name and the employee's number (which for the sake of this example is able to uniquely identify the employee).
public class Employee { private string employeeName; private int employeeNumber; public Employee(string employeeName, int employeeNumber) { this.employeeName = employeeName; this.employeeNumber = employeeNumber; } }Now create a unit test (right click on the class and choose Create Unit Tests and follow the wizard).
Let's declare some fields and initialise them in the MyTestInitialize method (in the commented and hidden "Additional test attributes" section).
private Employee andrew; private Employee rhona; private Employee rhonda; [TestInitialize()] public void MyTestInitialize() { andrew = new Employee("andrew", 2521); rhona = new Employee("rhona", 2791); rhonda = new Employee("rhonda", 2791); }Now employees rhona and rhonda are the same employee - just Rhona's name has been misspelt. But the employee numbers match. Let's write some tests to say that andrew isn't rhona or rhonda but rhona is the same as rhonda.
[TestMethod] public void AndrewIsntRhonaOrRhondaTest() { Assert.AreNotEqual(andrew, rhona); Assert.AreNotEqual(andrew, rhonda); }The first test method we write passes (as expected). Before we write some tests to check rhona against rhonda - let's just confirm that the references are as expected. In this case rhona and rhonda are separate objects, as well as just showing that if we copy a reference to an object that this is the same. Here we will use System.Object.ReferenceEquals method.
[TestMethod] public void CheckReferencesAreAsExpectedTest() { Employee e = andrew; Assert.AreEqual(e, andrew); Assert.IsTrue(Object.ReferenceEquals(e, andrew)); Assert.IsFalse(Object.ReferenceEquals(rhona, rhonda)); }Run this test and it works. Now what about Rhona and Rhonda - we want them to be the same person, so lets write a test for this
[TestMethod] public void RhonaIsRhondaTest() { Assert.AreEqual(rhona, rhonda); }But this test fails - as expected. The objects rhona and rhonda are separate objects. We wish them to be treated as if they are equal. Before we do this there are a couple of ways that we can do this - using the Equals method and also using the "==" operator (which we won't worry about). So lets write a test to check. Hopefully they should all fail. (It may well be that Assert.AreEqual as above calls Equals, but I'm not sure...)
[TestMethod] public void RhonaIsRhondaUsingEqualsTest() { Assert.IsTrue(rhona.Equals(rhonda)); }Now the tests for Rhona being Rhonda fail - as expected. We need to write some code. Any class that we want to use to represent a value should override Equals. Go back to the class and add a method to override Equals - if you type public override the intellisense will then be given a list of methods you can override. By default the code looks like.
public override bool Equals(object obj) { return base.Equals(obj); }If you try to compile you will receive a warning
"'Employee' overrides Object.Equals(object o) but does not override Object.GetHashCode()".For the moment we are going to ignore this (and explain this later). The Equals method takes one parameter (obj) and returns a bool. When overriding Equals one thing you must ensure is that if you compare the current instance to the other instance and the other instance is null, it should return false. So first thing, lets write a test
[TestMethod] public void EqualsComparedToNullTest() { Assert.IsFalse(rhona.Equals(null)); }And this works (before we change anything). Before we do write some code - let's examine the method signature of Equals. What type is the parameter? - it is object, not Employee. This means that we are going to need to check that our object is an Employee. Again a test for this - lets compare our Employee to a different type (in this case I'll use the EmployeeTest class)
[TestMethod] public void EqualsComparedToAnotherType() { Assert.IsFalse(rhona.Equals(new EmployeeTest())); }Now with that test working, lets finally write some code for Equals.
public override bool Equals(object obj) { if (obj == null) return false; Employee other = obj as Employee; if (other == null) return false; if (this.employeeNumber == other.employeeNumber) return true; else return false; }Let's add another Equals method - this one will take an Employee as a parameter.
public bool Equals(Employee obj) { if (obj == null) return false; if (this.employeeNumber == obj.employeeNumber) return true; else return false; }Wouldn't it be easier if this method is called - there is no casting here. And for all of the tests (bar the one we are comparing to a completely other type) we are comparing to an Employee. If you run the tests in the debugger and step through them you will see that the Assert.AreEquals(rhona, rhonda) calls the Equals with object as a parameter, but Assert.IsTrue(rhona.Equals(rhonda)) calls the Equals method with an Employee as parameter. The former calls a static method in object
public static bool Equals(object objA, object objB);The later is calling our Equals method directly. But more on that later - it is useful! We need to look at how we might use our Employee (e.g. in Lists etc.)
So a few things to deal with, which will be in subsequent posts
No comments:
Post a Comment