为什么两个新对象没有相同的哈希码?

6

我写了一个自定义比较器类。

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

当我新建两个项目并比较哈希码时,我的测试失败了。为什么哈希不同?

[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);
}

编辑 - 这是完整的类。我原本为了简洁而使用上面的例子,但需要更多信息

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>();
    }
}

这个GetHashCode的实现方式会为两个新对象返回不同的哈希值。
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();
}

3
这是你实际使用的代码吗?你没有使用比较器,而且只有在从“Item”隐式转换为“int”时才会编译。 - Lee
1
这不正确:int xHash = new Item();。我认为你的意思是 int xHash = comparer.GetHashCode(x); - Guffa
创建一个好的哈希码的一些注意事项:https://dev59.com/F3E85IYBdhLWcg3wNgvk#2890107 - Guffa
1
Item构造函数有什么作用?它是否初始化任何字段? - Rob
2
这些属性(Name, CompanyCode, ShipToDateAddress)的类型是什么?如果有任何一个类没有重写 GetHashCode 方法,那么不同的实例将会给出不同的值。你能展示一下 Item 类的代码吗? - juharr
显示剩余3条评论
2个回答

6

我假设您的某个属性中有复杂类型。因此:

当存在复杂类型时,每个实例化对象的GetHashCode都是不同的。

您可能需要实现一个更详细的GetHashCode方法。

我假设您想要联接每个属性的哈希码,例如像这样(未经测试):

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

这篇关于如何为结构体实现GetHashCode的帖子描述了多个属性(在这种情况下使用struct)的哈希码实现。


5
如果你阅读了 这篇文章,它说:“因为匿名类型的 Equals 和 GetHashCode 方法是基于属性的 Equals 和 GetHashCode 方法定义的,所以同一匿名类型的两个实例仅当它们的所有属性都相等时才相等。” - juharr
2
当然,如果“Address”是一个没有重写“GetHashCode”的类,这将无法工作。 - juharr
抱歉,我忘了提到地址是一个复杂类型。我还需要更新我的示例以适应地址属性。 - Boas Enkler

3

如果我定义Item类为:

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>(); 
    }
}

对我来说,你的测试通过了。这些属性的类型是什么,并且在无参构造函数中它们如何初始化?

编辑:根据您的更新 - 可能是在DtoPolicy 构造函数中创建的 new List<DtoClaim>()


我缩短了这个类,因为它有大约60个属性,类型可以是stringdecimalList<Item>DateTimeDateTime?bool - Jonathan Kittell
2
@JonKittell 那么你的问题在于 List<Item>,因为 List<T>GetHashCode 没有被重写,即使它们内部具有相同的引用,列表的哈希值也可能不同。 - juharr
@juharr - 这并不完全正确。如果列表引用为空或者引用相同(指向同一个列表),测试将会通过。 - Rob
1
获得更多源代码非常重要。但我认为问题在于(正如我们的答案所表明的那样),复杂类型具有默认的GetHashCode实现,每个实例都不同。 - Boas Enkler
我已经在问题中添加了完整的类。 - Jonathan Kittell
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接