CodeBetter.Com
CodeBetter.Com
RSS 2.0 via Feedburner
           Do you Twitter? Follow us @CodeBetter

Brendan Tompkins [MVP]

Blog First. Ask Questions Later.

Custom Entities, Equality and overriding GetHashCode()

OBSOLETE CONTENT
The author of this post has determined that this content is obsolete. Use at your own risk! Blog posts are a point-in-time snapshot of the blogger's thinking and should not be assumed to represent this blogger's current opinions. This post was left up for historical purposes.

Last week I posted about starting a new project here at the port.  I'm entering new development territory with this, specifically, I'm 1) Doing test-driven developmet with NUnit, 2) Using Custom Entities instead of DataSets and 3) Developing my buisness tier components as WSE services.   I've been having fun.  I'm very into the test-driven developent thing, NUnit Rocks!  I'm definitely writing much more solid code.  I'm going to post about that later, but I wanted to share some thoughts on using Custom Entities vs. DataSets here.

I'm adding a whole new layer to my Common tier, consisting of my new custom entities.  These are hopefully going to be the building blocks of our framework someday, and consist of our basic business entities. 

DataBinding: This was one thing I was slightly worried about with Custom Entities, but it's works great.  I've created strongly-typed collection classes for each entity, and I can databind these no problem.  Being used to the ease of  binding with DataSets, this was a relief.   The one thing I do miss is having the UI component to assist with the DataBinding which you get with DataSets, but that's not a big deal. 

Equality: This was somewhat of an eye opener for me.  As I was putting my entites into collections, I noticed that they were going in multiple times, because the hash codes for object that I consider equal were different.  Also, I noticed that if I created two entity objects with exactly the same data a test for equality would fail.  To fix this, I found I needed to override Equals(), ==, != and GetHashCode().

Now, there are some suggestionsfrom MS (Guidelines for Implementing Equals and the Equality Operator (==)) that give some guidelines for doing this, and Overview of the Object Class had some good pointers too,  but I discovered (via my trusty NUnit) that it's a little more complicated than it seems.  First, a warning:  The suggestions in this article are not correct.  In fact, even the comments that attempt to correct the arcticle are incorrect!  And, definitely don't do this!  

So after some testing, here's a few guidelines for overriding Equals(), ==, != and GetHashCode():

If you're testing for NULL using = = comparisons within your overrides for = =, make sure you use the base class' (object)  operator.  This may seem obvious, but you'll get stack overflows really quickly without code like this.

   public static bool operator ==(USCity x, USCity y)
   {
     
   if ((object) x == null) return ((object) y == null);
        return x.Equals(y);
    }

When overriding GetHashCode() make sure you test your reference types, like String, for NULL before bitwise - or'ing the hash codes.  For example, code like this will break if stateCode or city are NULL:

   public override int GetHashCode()
   {
         return this.stateCode.GetHashCode() ^
this.city.GetHashCode();
    }

This may seem obvious as well, but it took me some Unit testing to realize this.  So, once I got everything straight, my simple class ended up looking like this:

public class USCity
{

   private string city;
  
private string stateCode;

   .. Public Properties Removed for Brevity ..

  
public static bool operator ==(USCity x, USCity y)
  
{
       
if ((object) x == null) return ((object) y == null);
       
return x.Equals(y);
   
}

   public static bool operator !=(USCity x, USCity y) 
   
{
      
return !(x == y);
   
}

  
public override bool Equals(object obj)
  
{
      
if(obj is USCity)
         
if(((USCity)obj).stateCode == this.stateCode &&
             
((USCity)obj).city == this.city) 
                
return true;
      
return false;
  
}

  
public override int GetHashCode()
  
{
        
int hashCode = 0;
        
if(this.stateCode != null) hashCode ^= this.stateCode.GetHashCode();
        
if(this.city != null) hashCode ^= this.city.GetHashCode();
        
return hashCode;
    
}
}

And here's my UnitTest that I used to sort this out this equality stuff:

[Test]
public void TestCityEquality()
{
      USCity city =
null;
      USCity city2 =
null;

      Assert.AreEqual(city, city2, "Nulls Should be Equal");

     city =
new USCity();
     city.City = "Norfolk";
     city.StateCode = "VA";
     Assert.IsTrue(city != city2, "Should not be Equal");

      city2 = new USCity();
      city2.City = "Norfolk";
      city2.StateCode = "VA";
      Assert.IsTrue(city == city2, "Should be Equal");

      city2.City = "Portsmouth";
      Assert.IsTrue(city != city2, "Should not be Equal");
}

-Brendan



Comments

Raymond Chen said:

If you allow users to change the StateCode then you are violating the third rule of hash codes:

"The hash function must return exactly the same value regardless of any changes that are made to the object."

This results in badness like the following:

USCity city = new USCity();
city.City = "Norfolk";
city.StateCode = "VA";
Hashtable hash = new Hashtable();
hash[city] = 25;
city.City = "Richmond"; // uh oh this changes the hashcode

USCity city2 = new USCity();
city2.City = "Norfolk";
city2.StateCode = "VA";
System.Console.WriteLine("NorfolkVA = {0}", hash[city2]);
city2.City = "Richmond";
System.Console.WriteLine("RichmondVA = {0}", hash[city2]);

Notice that the object "city" is in the hashtable but you can't find it under either its old name or its new name.


# July 9, 2004 5:41 AM

Brendan Tompkins said:

Raymond... I see. So what kind of hashing algorithm do I need here?

# July 9, 2004 6:16 AM

Brendan Tompkins said:

So if I only let you set the internals via the constructor, and removed the public setter that would work, right? Would that have any effect on serialization?
# July 9, 2004 6:19 AM

Brendan Tompkins said:

Darrell...

Unit testing has been fun, but Raymond's comments point out one problem I've had so far: Your tests are only as good as your knowledge of what may go wrong!
# July 9, 2004 7:07 AM

Darrell said:

Or you could look at it as, should this test still pass if I changed the constituents of the hashcode?
# July 9, 2004 7:10 AM

Brendan Tompkins said:

Right. I wasn't even testing for HashCodes not mutating, I didn't even know the rule!
# July 9, 2004 7:14 AM

Mark Bonafe said:

Brendan, if you want to make your type-safe collections work as UI components, you can do so quite easily.

First, add these attributes to the class declaration:
[ToolboxItem(true)]
[DesignTimeVisible (true)]
[Serializable]

Then make sure to implement IComponent and IDisposable. Like this:
public class AddressList : <Collection/ArrayList/etc>, IComponent, IDisposable

I know you are already implementing plenty of other interfaces, but these are easy compared to the rest. Here are the required implementations:
#region IComponent Members
// Added to implement Site property correctly.
private ISite _site = null;

/// <summary>
/// Get/Set the site where this data is
/// located.
/// </summary>
public ISite Site
{
get { return _site; }
set { _site = value; }
}
#endregion

#region IDisposable Members
/// <summary>
/// Notify those that care when we dispose.
/// </summary>
public event System.EventHandler Disposed;

/// <summary>
/// Clean up. Nothing here though.
/// </summary>
public void Dispose()
{
// Nothing to clean.
if (Disposed != null)
Disposed (this, EventArgs.Empty);
}
#endregion


That's all there is to it. You can now add the type-safe collection to your toolbox and drag it on to a form or web page!
# July 9, 2004 7:43 AM

Brendan Tompkins said:

Cool Mark! I'll have to try this.
# July 9, 2004 8:21 AM

DarthPedro said:

This is an interesting discussion on what to look out for when implementing GetHashCode().
# July 9, 2004 1:19 PM

Chris Taylor said:

Brendan, you might find a post I made sometime back interesting. http://dotnetjunkies.com/WebLog/chris.taylor/archive/2004/01/09/5393.aspx
# July 10, 2004 4:44 PM

TrackBack said:

# February 22, 2005 12:51 PM

TrackBack said:

# March 1, 2005 1:50 PM

TrackBack said:

# March 1, 2005 1:52 PM

Yllusyo said:

This is an old thread I know, but just for the people that tend to find and read this article.

The suggestion about the hashcode required to be stable for the lifetime of the object is totally wrong.

The exact rule is: if you are using a hashtable, the key you use for that table should be immutable for the lifetime of the object. This has nothing to do with a correct GetHashCode implementation (although the hashtable will call this function to determine in which bucket the object will have to be put)

The two only rules that really apply are:

- the distribution should be random and equally distributed

- if two objects are equal, they should render the same hash

If you put the object in a hashtable, create an extra property that provides you a stable hash and use that key to store in the hashtable. Don't ever couple the implementation of GetHashCode with the Hash code to put in an HashTable.

# April 18, 2008 4:17 AM

Leave a Comment

(required)  
(optional)
(required)  

Enter the numbers above:
Add

About Brendan Tompkins

Brendan has been programming with .NET since the first public beta and is owner and operator of Port Technology Services, a consultancy company providing .NET application development services to the Maritime industry. In July, 2007, he was awarded the Microsoft MVP award for ASP.NET. He's also a proud co-founder of failed .COM startup Intrinsigo, and has had a hand in the failure of numerous other businesses. He currently runs CodeBetter.Com and Devlicio.us, and lives in Norfolk, Virgina with his wife Tiara and son Ian.

View Brendan's profile on LinkedIn

Check out Devlicio.us!

Our Sponsors