views:

256

answers:

3

I have an Address class in C# that looks like this:

public class Address
{            
    public string StreetAddress { get; set; }
    public string RuralRoute { get; set; }
    public string City { get; set; }
    public string Province { get; set; }
    public string Country { get; set; }
    public string PostalCode { get; set; }
}

I'm implementing equality and so I need to override the hash code. At first I was going to use the hashcode formula from EJ but then I thought: These are all string fields, can't I just just use a StringBuilder to concatenate them and return the hash code from that string?

That is:

var str = new StringBuilder();
str.Append(StreetAddress)
   .Append(RuralRoute)
   ...

return str.ToString().GetHashCode();

What are the advantages/disadvantages of this? Why shouldn't I do it?

+2  A: 

Don't do that because the objects can be different altough the hashcode is the same.

Think of

"StreetAddress" + "RuralRoute" + "City"

vs

"Street" + "AddressRural" + "RouteCity"

Both will have the same hashcode but different content in the fields.

Kosi2801
That's a good point, I hadn't even considered that. Although it does seem unlikely to happen in practice.
cdmckay
+6  A: 

I would avoid doing that simply on the grounds that it creates a bunch of strings pointlessly - although Kosi2801's point about making collisions simple is also relevant. (I suspect it wouldn't actually create many collisions, due to the nature of the fields, but...)

I would go for the "simple and easy to get right" algorithm I've previously used in this answer (thanks for looking it up lance :) - and which is listed in Effective Java, as you said. In this case it would end up as:

public int GetHashCode()
{
    int hash = 17;
    // Suitable nullity checks etc, of course :)
    hash = hash * 23 + StreetAddress.GetHashCode();
    hash = hash * 23 + RuralRoute.GetHashCode();
    hash = hash * 23 + City.GetHashCode();
    hash = hash * 23 + Province.GetHashCode();
    hash = hash * 23 + Country.GetHashCode();
    hash = hash * 23 + PostalCode.GetHashCode();
    return hash;
}

That's not null-safe, of course. If you're using C# 3 you might want to consider an extension method:

public static int GetNullSafeHashCode<T>(this T value) where T : class
{
    return value == null ? 1 : value.GetHashCode();
}

Then you can use:

public int GetHashCode()
{
    int hash = 17;
    // Suitable nullity checks etc, of course :)
    hash = hash * 23 + StreetAddress.GetNullSafeHashCode();
    hash = hash * 23 + RuralRoute.GetNullSafeHashCode();
    hash = hash * 23 + City.GetNullSafeHashCode();
    hash = hash * 23 + Province.GetNullSafeHashCode();
    hash = hash * 23 + Country.GetNullSafeHashCode();
    hash = hash * 23 + PostalCode.GetNullSafeHashCode();
    return hash;
}

You could create a parameter array method utility to make this even simpler:

public static int GetHashCode(params object[] values)
{
    int hash = 17;
    foreach (object value in values)
    {
        hash = hash * 23 + value.GetNullSafeHashCode();
    }
    return hash;
}

and call it with:

public int GetHashCode()
{
    return HashHelpers.GetHashCode(StreetAddress, RuralRoute, City,
                                   Province, Country, PostalCode);
}

In most types there are primitives involved, so that would perform boxing somewhat unnecessarily, but in this case you'd only have references. Of course, you'd end up creating an array unnecessarily, but you know what they say about premature optimization...

Jon Skeet
Another solution is to use EqualityComparer<T>.Default.GetHashCode(someValue). This is a null safe hashing mechanism and is in the framework since 2.0
JaredPar
Thanks, that works perfect. There is a small error in your GetNullSafeHashCode() method: it's missing the "this".
cdmckay
Doh! Thanks very much. Fixed.
Jon Skeet
A: 

For this sort of thing, you might want to implement IEqualityComparer<Address>:

public class Address : IEqualityComparer<Address>
{        
    //
    // member declarations
    //

    bool IEqualityComparer<Address>.Equals(Address x, Address y)
    {
        // implementation here
    }

    int IEqualityComparer<Address>.GetHashCode(Item obj)
    {
        // implementation here
    }
}

You could also implement IComparable<Address> to get ordering...

Keltex