6

I wrote a custom comparer class.

public class ItemComparer : IEqualityComparer<Item>
{
    public int GetHashCode(Item x)
    {
        return (x == null) ? 0 : new { x.Name, x.CompanyCode,
           x.ShipToDate, x.Address }.GetHashCode();
    }

I have a test that fails when I new up two items and compare the hash codes. Why are the hashes different?

[TestMethod]
public void Two_New_Items_Have_The_Same_Hash_Code()
{
     // arrange
     var comparer = new ItemComparer();
     Item x = new Item();
     Item y = new Item();

     // act
     int xHash = comparer.GetHashCode(x);
     int yHash = comparer.GetHashCode(y);

     // assert
     Assert.AreEqual(xHash, yHash);
}

EDIT - Here is the complete class. I orginally used the example above for brevity but more information is needed

public class DtoPolicy : DtoBase
{
    [Description("The Policy Number")]
    public string PolicyNumber { get; set; }
    [Description("The Agent Code for the Agent who wrote the policy.")]
    public string AgentCode { get; set; }
    [Description("The First Name of the insured")]
    public string FirstName { get; set; }
    [Description("The Last Name of the insured")]
    public string LastName { get; set; }
    [Description("The Date of Birth of the insured")]
    public DateTime DateOfBirth { get; set; }
    [Description("The Age of the insured")]
    public int Age { get; set; }
    [Description("The Issue Date of the Policy")]
    public DateTime PolicyIssueDate { get; set; }
    [Description("The current status of the policy")]
    public string PolicyStatus { get; set; }
    public string TypeOfCoverage { get; set; }
    public string PlanDescription { get; set; }
    public decimal CompanyCode { get; set; }
    public DateTime? TerminationDate { get; set; }
    public decimal PolicyHolderSSN { get; set; }
    [Description("The Zip Code of the insured")]
    public string ZipCode { get; set; }
    public decimal OwnerSSN { get; set; }
    public string EmailAddress { get; set; }
    public string WebUsername { get; set; }
    public string OwnerFirstName { get; set; }
    public string OwnerLastName { get; set; }
    public string PayorFirstName { get; set; }
    public string PayorLastName { get; set; }
    public DateTime? PolicyEffectiveDate { get; set; }
    public string AgentName { get; set; }
    public string AgentPhone { get; set; }
    public string InsuredCityState { get; set; }
    public string InsuredAddress1 { get; set; }
    public string InsuredAddress2 { get; set; }
    public string InsuredCity { get; set; }
    public string InsuredState { get; set; }
    public string InsuredPhone { get; set; }
    public string OwnerAddress1 { get; set; }
    public string OwnerAddress2 { get; set; }
    public string OwnerCity { get; set; }
    public string OwnerState { get; set; }
    public string OwnerZip { get; set; }
    public string OwnerPhone { get; set; }
    public string PayorAddress1 { get; set; }
    public string PayorAddress2 { get; set; }
    public string PayorCity { get; set; }
    public string PayorState { get; set; }
    public string PayorZip { get; set; }
    public string PayorPhone { get; set; }
    public DateTime? PaidToDate { get; set; }
    public DateTime? LastPaidDate { get; set; }
    public string PremiumMode { get; set; }
    public decimal PremiumAmount { get; set; }
    public DateTime? LastBillDate { get; set; }
    public string BillingStatus { get; set; }
    public decimal TotalLoanAmount { get; set; }
    public decimal DividendAccumulation { get; set; }
    public decimal ModalPremiumMonthly { get; set; }
    public decimal ModalPremiumSemiAnnual { get; set; }
    public decimal ModalPremiumQuarterly { get; set; }
    public decimal ModalPremiumAnnual { get; set; }
    public bool ElectronicBilling { get; set; }
    public List<DtoClaim> Claims { get; set; }
    public decimal MarketCode { get; set; }
    public string BillingMode { get; set; }

    public DtoPolicy()
    {
        Claims = new List<DtoClaim>();
    }
}

This implementation of GetHashCode also returns different hashes for two new objects

public int GetHashCode(DtoPolicy x)
{
    return (x == null) ? 0 : x.Age.GetHashCode() ^ x.AgentCode.GetHashCode() ^ x.AgentName.GetHashCode() ^ x.AgentPhone.GetHashCode() ^
        x.BillingMode.GetHashCode() ^ x.BillingStatus.GetHashCode() ^
        x.Claims.GetHashCode() ^ x.CompanyCode.GetHashCode() ^ x.DateOfBirth.GetHashCode() ^ x.DividendAccumulation.GetHashCode() ^
        x.ElectronicBilling.GetHashCode() ^ x.EmailAddress.GetHashCode() ^ x.FirstName.GetHashCode() ^ x.InsuredAddress1.GetHashCode() ^
        x.InsuredAddress2.GetHashCode() ^ x.InsuredCity.GetHashCode() ^ x.InsuredCityState.GetHashCode() ^ x.InsuredPhone.GetHashCode() ^
        x.InsuredState.GetHashCode() ^ x.LastBillDate.GetHashCode() ^ x.LastName.GetHashCode() ^
        x.LastPaidDate.GetHashCode() ^ x.MarketCode.GetHashCode() ^ x.ModalPremiumAnnual.GetHashCode() ^ x.ModalPremiumMonthly.GetHashCode() ^
        x.ModalPremiumQuarterly.GetHashCode() ^ x.ModalPremiumSemiAnnual.GetHashCode() ^ x.OwnerAddress1.GetHashCode() ^ 
        x.OwnerAddress2.GetHashCode() ^ x.OwnerCity.GetHashCode() ^ x.OwnerFirstName.GetHashCode() ^
        x.OwnerLastName.GetHashCode() ^ x.OwnerPhone.GetHashCode() ^ x.OwnerSSN.GetHashCode() ^ x.OwnerState.GetHashCode() ^ 
        x.OwnerZip.GetHashCode() ^ x.PaidToDate.GetHashCode() ^ x.PayorAddress1.GetHashCode() ^
        x.PayorAddress2.GetHashCode() ^ x.PayorCity.GetHashCode() ^ x.PayorFirstName.GetHashCode() ^ x.PayorLastName.GetHashCode() ^
        x.PayorPhone.GetHashCode() ^ x.PayorState.GetHashCode() ^ x.PayorZip.GetHashCode() ^
        x.PlanDescription.GetHashCode() ^ x.PolicyEffectiveDate.GetHashCode() ^ x.PolicyHolderSSN.GetHashCode() ^ 
        x.PolicyIssueDate.GetHashCode() ^ x.PolicyNumber.GetHashCode() ^ x.PolicyStatus.GetHashCode() ^
        x.PremiumAmount.GetHashCode() ^ x.PremiumMode.GetHashCode() ^ x.TerminationDate.GetHashCode() ^
        x.TotalLoanAmount.GetHashCode() ^ x.TypeOfCoverage.GetHashCode() ^ x.WebUsername.GetHashCode() ^ x.ZipCode.GetHashCode();
}
Jonathan Kittell
  • 7,163
  • 15
  • 50
  • 93
  • 3
    Is this your actual code? You aren't using the comparer anywhere, and this will only compile if there's an implicit conversion from `Item` to `int`. – Lee May 29 '15 at 12:35
  • 1
    This doesn't seem right: `int xHash = new Item();`. I think that you mean `int xHash = comparer.GetHashCode(x);`. – Guffa May 29 '15 at 12:37
  • Sorry, I fixed the `act` part, I mistyped it. – Jonathan Kittell May 29 '15 at 12:40
  • Some notes on creating a good hash code: http://stackoverflow.com/questions/2890040/implementing-gethashcode/2890107#2890107 – Guffa May 29 '15 at 12:44
  • 1
    What does the Item constructor do? Does it initialize any of the fields? – Rob May 29 '15 at 12:45
  • 2
    What are the types of the properties (`Name`, `CompanyCode`, `ShipToDate`, and `Address`)? If any are classes that do not override `GetHashCode` then different instances will give different values. Can you show the code for the `Item` class? – juharr May 29 '15 at 12:45
  • Do you perhaps initialize `ShipToDate` with `DateTime.Now`? That will cause each `Item` to have a different value for this property. – Dennis_E May 29 '15 at 12:53
  • If I new up the item the `DateTime` properties all have a value of `01/01/0001 12:00:00 AM` – Jonathan Kittell May 29 '15 at 12:56

2 Answers2

6

I assume that you have complex types in one of your proeprties. Therefore: When having complex type the GetHashCode is dirffent for each instanciated object.

You may have to implement a more detailed gethashcode method.,

I assume you want to join the hashcodes of each property for example like this(untested)

x.Name.GetHashCode() ^ x.CompanyCode.GetHashCode() ^ x.ShipToDate.GetHashCode() ^  x.Address.Id

This post on how to implement GetHashCode for structure describes the hashcode implementation for multiple properties (in this case with a struct).

Community
  • 1
  • 1
Boas Enkler
  • 12,264
  • 16
  • 69
  • 143
  • 5
    If you read [this](https://msdn.microsoft.com/en-us/library/bb397696.aspx) it says "Because the Equals and GetHashCode methods on anonymous types are defined in terms of the Equals and GetHashCode methods of the properties, two instances of the same anonymous type are equal only if all their properties are equal." – juharr May 29 '15 at 12:39
  • 2
    This of course will not work if `Address` is a class that does not override `GetHashCode`. – juharr May 29 '15 at 12:46
  • Ups soirry forgot to mention that i assume that adress is a compex type. Also have to update my exmaple to the adress proeprty – Boas Enkler May 29 '15 at 12:54
3

If I define the Item class as

public class Item
{
    public string Name { get; set; }
    public string CompanyCode { get; set; }
    public DateTime ShipToDate { get; set; }
    public List<string> Address { get; set; }

    public Item()
    {
        //Uncomment Address initialization and the test fails..
        //Address = new List<string>(); 
    }
}

Then your test passes for me. What are the types of these properties, and how are they initialized in the parameterless constructor?

Edit: Following your update - it's presumably the new List<DtoClaim>() created in the DtoPolicy constructor.

Rob
  • 4,327
  • 6
  • 29
  • 55
  • I shortened the class because it has about 60 properties in it, which the types can be `string`, `decimal`, `List`, `DateTime`, `DateTime?` and `bool`. – Jonathan Kittell May 29 '15 at 12:52
  • 2
    @JonKittell Then your issue is with the `List` as `GetHashCode` for `List` is not overriden and you'll likely get a different hash for lists even if they have identical references inside of them. – juharr May 29 '15 at 12:54
  • @juharr - that isn't strictly true. The test will pass if either the list references are null, or if the list references are identical (point to the same list). – Rob May 29 '15 at 12:58
  • I commented out only the `GetHashCode()` for the list and the test still failed. – Jonathan Kittell May 29 '15 at 12:59
  • @Rob true, but I doubt they point at the same list and probably are not `null` or what they have would work. I'm guessing that the list is instantiated in the `Item` constructor. Long story short we don't have the whole story yet. – juharr May 29 '15 at 13:00
  • 1
    It would be important to get more source code. But i think the problem is (like our answers indicate) that complex types have a default GetHAshcode Implementation that varies for each instance – Boas Enkler May 29 '15 at 13:02
  • @JonKittell What do you mean by "commented out only the `GetHashCode()` for the list"? Do you mean you remove it from the anonymous class or that you are using Boas Enkler's answer? Long story short we need the complete definition of the `Item` class to help you. – juharr May 29 '15 at 13:03
  • I have added the complete class in the question. – Jonathan Kittell May 29 '15 at 13:18
  • @Jon Kittell - I updated my answer - it looks like it's the two empty list members that cause the difference. You instantiate new Lists in the DtoPolicy constructor. – Rob May 29 '15 at 13:28