如何检查两个对象是否相等,只考虑它们的属性而不破坏现有的Object.Equals()方法?

20
基本上,即使它们包含相同的属性值,GethashCode也会有所不同...那么为什么默认返回不同的哈希代码呢?
public class User
{
    public Int32 Id { get; set; }
    public String Username { get; set; }
}

User a = new User();
a.Id = 1;
a.Username = "Hello";

User b = new User();
b.Id = 1;
b.Username = "Hello";

Console.WriteLine("Hash A: {0} | Hash B: {1}", a.GetHashCode(), b.GetHashCode());
//Hash A: 37121646 | Hash B: 45592480 <-- these values change each time I rerun the app?
有没有更合适的方法,使我不会破坏Object.Equals对我的对象的工作方式,但仍然能够根据参数值进行自己的相等性检查?我问这个问题的原因是因为我有一个服务:SynchronizeUsers(),它会下载一组用户。我不想清除我的用户缓存,而是希望只更新需要更新的用户,删除同步指示删除的用户并添加新用户。但是,我不能在这些对象上只使用Object.Equals()。

3
哈希码在应用程序运行过程中发生变化是正常的。但是,如果 a.GetHashCode() != b.GetHashCode(),则不正常,除非编写该类的人故意将其设置为有缺陷,或该类具有重要成员会自动初始化为非固定值(例如 DateTime.Now)。请注意,这里不包括解释性内容。 - Jon
1
还有,这里的问题是什么?(a)如何使“Equals”返回true,还是(b)与“GetHashCode”相关的内容? - Jon
10个回答

42

虽然已经晚了回答,但是可能会有人来到这里,我需要知道我的想法是对还是错。 如果严格考虑值,为什么不将对象转换成JSON并比较JSON字符串呢?像这样:

如果严格考虑值,为什么不将对象转换成JSON并比较JSON字符串呢?像这样:

if (JsonConvert.SerializeObject(obj1) == JsonConvert.SerializeObject(obj2)) continue;

4
最佳答案,因为它允许代码拥有动态对象。 - Aesir
1
如此简单,却如此聪明。 - DespeiL

18
您是否尝试过实现自己的IEqualityComparer?您可以将其传递给.Equals()重载,以定义自己的自定义相等逻辑,即使属性x、y、z相同,User A = User B仍然相等。请参见:MSDN。 编辑:我应该写可以实例化您的EqualityComparer并将两个实例传递到其Equals()方法中并获取一个布尔值。基本控制台应用程序...会显示true、false、false。这件事很琐碎,只有两个所示的属性。
var comparer = new ThingEqualityComparer();

Console.WriteLine(comparer.Equals(new Thing() { Id = 1, Name = "1" }, new Thing() { Id = 1, Name = "1" }));
Console.WriteLine(comparer.Equals(new Thing() { Id = 1, Name = "1" }, new Thing() { Id = 2, Name = "2" }));
Console.WriteLine(comparer.Equals(new Thing() { Id = 1, Name = "1" }, null));


class ThingEqualityComparer : IEqualityComparer<Thing>
{
    public bool Equals(Thing x, Thing y)
    {
        if (x == null || y == null)
            return false;

        return (x.Id == y.Id && x.Name == y.Name);
    }

    public int GetHashCode(Thing obj)
    {
        return obj.GetHashCode();
    }
}

你说得没错,虽然会增加更多的代码量,但正如ebyrob的评论所指出的,最佳实践仍然是覆盖GetHashCode(),并且IEqualityComparer只有两个方法Equals()和GetHashCode()需要被覆盖。个人而言,我更喜欢将这种“业务逻辑”分离出来;一个单独的比较器也更容易与控制反转(IoC)相结合……但最终覆盖Equals()也可以很好地工作。 - pelazem
1
@Nix:如果这段代码需要用于实现应用程序的业务逻辑,那么我会称其为非常必要 - Jon
为什么在一个类还没有自己的默认相等比较器时,你会为它添加一个自定义的IEqualityComparer?明显数据集中已经有一个明显的相等比较方式,为什么不使用它呢? - user645280
@ebyrob,原帖中的@michael说他由于某种原因不想或不能使用object.Equals()。因此,IEqualityComparer可以满足他的需求。对我来说,我也看了他的情况,并认为这种方式将为他提供未来根据上下文新建比较器的灵活性,即IoC,而不是在对象中“硬编码”相等逻辑;尽管他当然没有要求这样做。 - pelazem
我刚刚发布了一个答案,说明了ReSharper如何自动完成这个操作,供您参考。 :-) - RJB
显示剩余4条评论

14

如果您安装了ReSharper(它非常实用!),那么您只需要:

Alt+Insert

当你的光标在类内时,部分类适用于隐藏样板文件。

它将自动为每个属性实现相等性检查。

(使用Ctrl+A选择所有属性,您可以通过Space键选中所有属性!)


3

2

以下是一种解决方案,它不需要在类中使用任何自定义逻辑,并使用泛型来确保编译时obj1和obj2的类型相同:

  public static class ObjectComparerUtility
  {
    public static bool ObjectsAreEqual<T>(T obj1, T obj2)
    {
      var obj1Serialized = JsonConvert.SerializeObject(obj1);
      var obj2Serialized = JsonConvert.SerializeObject(obj2);

      return obj1Serialized == obj2Serialized;
    }
  }

使用方法:

  var c1 = new ConcreteType { Foo = "test1" };
  var c2 = new ConcreteType { Foo = "test1" };
  var areEqual = ObjectComparerUtility.ObjectsAreEqual(c1, c2);
  Assert.IsTrue(areEqual);

0
为什么不编写自己的相等方法呢?例如,
User a = new User();
a.Id = 1;
a.Username = "Hello";
User b = new User();
b.Id = 1;
b.Username = "Hello";
a.IsEqualTo(b);

其中IsEqualTo在您的用户类中定义为:

Public bool IsEqualTo(user compareTo)
{
  return (UserName == compareTo.UserName && Id == compareTo.Id);
}

1
我刚刚发布了一个答案,说明了ReSharper如何自动完成这个操作,供您参考。 :-) - RJB

0
如果你只是想要哈希一些东西,可以考虑使用扩展方法来生成哈希。
public static int GenerateHash(this User myUser){
    return myUser.UserName.GetHashCode() ^ ... other properties....
}

然后在你的代码中,你可以这样做:

Console.WriteLine("Hash A: {0} | Hash B: {1}", a.GenerateHash(), b.GenerateHash());

这样做可以保持一切不变,不应该破坏其他任何东西。如果您正在寻找一种比较对象的方法,您可以使用扩展方法来完成相同的操作:

public static int AreEqual(this User myUser, User someOther){
    return myUser.UserName == someOther.UserName && ...  other properties.
}

使用方法如下:

if(a.AreEqual(b)){
    // these are equal have fun.
}

0
提供的代码定义了一个Person类,其中包含FirstName和LastName属性。该类重写了Equals和GetHashCode方法,用于比较对象和生成对象的哈希码。
Equals方法通过检查两个Person对象是否属于相同类型并具有相同的FirstName和LastName值来比较它们。如果任何条件不满足,则该方法返回false;否则,返回true。
GetHashCode方法使用哈希码计算算法,将FirstName和LastName属性的哈希码组合起来为Person对象生成一个哈希码。这对于在基于哈希的集合(如字典或哈希集)中使用Person对象是必要的。
这种Equals和GetHashCode的实现通常是正确的,并遵循重写这些方法的准则。它确保如果两个Person对象具有相同的FirstName和LastName值,则被视为相等。此外,哈希码的计算基于相同的属性,为基于哈希的集合提供一致的行为。
public class Person
{
       public string FirstName { get; set; }
       public string LastName { get; set; }
   
       public override bool Equals(object obj)
       {
           if (obj == null || GetType() != obj.GetType())
           {
               return false;
           }
   
           Person otherPerson = (Person)obj;
   
           return FirstName == otherPerson.FirstName && LastName == otherPerson.LastName;
       }
   
       public override int GetHashCode()
       {
           int hashCode = 17;
           hashCode = hashCode * 23 + FirstName.GetHashCode();
           hashCode = hashCode * 23 + LastName.GetHashCode();
           return hashCode;
       }
}


您的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认您的答案是正确的。您可以在帮助中心中找到有关如何编写良好答案的更多信息。 - moken

-1

添加一个方法:

public class User
{
    public int UserID { get; set; }

    public bool IsUser(object obj) 
    {
        return (obj is User && ((User)obj).UserID == this.UserID);
    }
}

1
-1:这是一个糟糕的想法,因为它没有考虑到“Equals”和“GetHashCode”之间的关系。此外,它只适用于您可以修改类的情况。 - Jon
@FlyingStreudel:那不是正确的。契约规定,当两个对象比较相等时,它们的哈希码必须相等。你的解决方案改变了相等比较的结果,但并不确保当它返回“true”时哈希码也相等。[另外:a和b是不同的对象实例告诉我们关于它们的哈希码完全没有任何信息] - Jon
@Jon - 他正在使用数组,而不是哈希,但这是正确的。[另外:如果您没有实现自己的 GetHashCode(),我认为除非它们是同一实例,否则不能保证相等] - FlyingStreudel
@FlyingStreudel:我在那段代码中没有看到任何数组。此外,文档(http://msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx)也支持我的观点:“如果两个对象比较相等,则每个对象的GetHashCode方法必须返回相同的值。但是,如果两个对象不相等,则两个对象的GetHashCode方法不必返回不同的值。” - Jon
@Jon - 我问这个问题的原因是因为我有一个服务:SynchronizeUsers(),它会下载一组用户。同时,我同意你的看法,但你刚才发表的观点正是我所说的... 两个不相等的对象(即两个单独的实例)可以具有相同的哈希值,但不能保证。你需要实现自己的 GetHashCode() 来确保这一点。 - FlyingStreudel
显示剩余6条评论

-3

字符串数组是用于比较的排除列表。它使用反射,但性能非常好。请查看Apache Commons Lang 3库。

CompareToBuilder.reflectionCompare(arg0, arg1, new String[]{"UID", "uidcount"})

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