如何判断两个对象是否相等(C#)

31
我希望了解比较两个对象并确定它们是否相等的最佳方法。我正在重写GethashCode和Equals。因此,一个基本的类如下所示:
public class Test
{
    public int Value { get; set; }
    public string String1 { get; set; }
    public string String2 { get; set; }

    public override int GetHashCode()
    {
        return Value ^ String1.GetHashCode() ^ String2.GetHashCode();
    }

    public override bool Equals( object obj )
    {
        return GetHashCode() == obj.GetHashCode();
    }
}

为了测试目的,我创建了两个对象:

Test t = new Test()
{
    Value = 1,
    String1 ="One",
    String2 = "One"
};

Test t2 = new Test()
{
    Value = 1,
    String1 = "Two",
    String2 = "Two"
};

bool areEqual = t.Equals( t2 );

在测试中,即使两个对象不同,areEqual也返回true。我意识到这是因为String1和String2在每个对象中的值相同,当进行哈希时会互相抵消。

是否有更好的哈希方法可以解决我的问题?

6个回答

41

您当前的相等性方法存在问题 - 值的数量超过了可能的哈希码。偶尔会出现不相等但给出相同哈希值的值是完全合理(也是预期的)。Equals应该检查实际的值:

public override bool Equals(object obj)
{
    Test test = obj as Test;
    if (obj == null)
    {
        return false;
    }
    return Value == test.Value &&
        String1 == test.String1 &&
        String2 == test.String2;
}

需要注意以下几点:

  • Your way of generating the hashcode will give the same value for any fixed Value if String1 and String2 are the same; it will also blow up if String1 or String2 is null. This is an unfortunate aspect of using XOR for hashing. I prefer something like this:

    // Put this extension method in a utility class somewhere
    public static int SafeGetHashCode<T>(this T value) where T : class
    {
        return value == null ? 0 : value.GetHashCode();
    }
    
    // and this in your actual class
    public override int GetHashCode()
    {
        int hash = 19;
        hash = hash * 31 + Value;
        hash = hash * 31 + String1.SafeGetHashCode();
        hash = hash * 31 + String2.SafeGetHashCode();
        return hash;
    }
    
  • Generally speaking, equality becomes tricky when inheritance gets involved. You may want to consider sealing your class.

  • You may also want to implement IEquatable<Test>


3
关于 SafeGetHashCode 的问题 - 只是提一下,EqualityComparer<T>.GetHashCode 做的事情非常相似 - 不过这个扩展方法还是挺整洁的,哈哈 ;p - Marc Gravell
你的回答非常好,但第一个单词是错误的:“No”是对他的问题的错误答案:“是否有更好的方法…” 此外,Simons测试类没有基类,也没有实现接口,因此override关键字是错误的。 - OlimilOops
1
@Oops:会修复“无”问题...但是Test类确实有一个基类——System.Object的隐式基类。这些方法确实覆盖了Object的实现。 - Jon Skeet

17

你的Equals方法是不正确的 - 它应该定义出两个东西相等的意义,而具有相同的哈希码并不意味着它们相等(然而,一个不同的哈希码确实意味着它们不相等)。如果“相等”意味着“两个字符串是成对相等的”,那么请测试一下。

关于更好的哈希函数; 用异或运算符来实现的哈希函数是臭名昭著的,因为通过将一个值与自身异或可以轻松得到0。更好的方法可能是像这样:

int i = 0x65407627;
i = (i * -1521134295) + Value.GetHashCode();
i = (i * -1521134295) + (String1 == null ? 0 : String1.GetHashCode());
i = (i * -1521134295) + (String2 == null ? 0 : String2.GetHashCode());
return i;

(更新,借用C#编译器用于匿名类型的种子和乘数) - Marc Gravell
你为什么选择这些值来生成哈希码? - CARLOS LOTH
@Carlos:请看Marc之前的评论 :) - Jon Skeet
@Jon:我以前也见过这个,但是我没能理解。感谢你尝试帮忙。 - CARLOS LOTH
@Carlos - 老实说,我不知道微软为什么会选择那些特定的值 - 种子+因子方法已经很常见了。这些值可以是任何能够产生合理分散效果的东西(负数可能有所帮助)。 - Marc Gravell

7
简单
Object.Equals(obj1, obj2);

2
这完全取决于obj1/obj2是否为引用类型。请参阅http://msdn.microsoft.com/en-us/library/bsc2ak47.aspx。 - Lee Taylor
1
这实际上是最好的答案,它是最简洁、最清晰和100%正确的。 - Aviad P.

2
对于任意两个对象,对象相等意味着哈希码相等,但是,哈希码相等不意味着对象相等。来自 MSDN 上的 Object.GetHashCode

哈希函数必须具有以下属性:

如果两个对象比较相等,则每个对象的 GetHashCode 方法必须返回相同的值。但是,如果两个对象不比较相等,则两个对象的 GetHashCode 方法不必返回不同的值。

换句话说,你的 Equals 方法写错了。它应该像这样:
public override bool Equals(object obj)
{
    Test other = obj as Test;
    if (other == null)
        return false;

    return (Value == other.Value)
        && (String1 == other.String1)
        && (String2 == other.String2);
}

GetHashCode 适用于集合(如 Dictionary)以快速确定近似相等性。 Equals 用于比较两个对象是否真的相同。

2
你可以将这两个对象序列化为JSON,然后比较这两个字符串是否相同。
例如:
JavaSriptSerializer serialiser = new JavaScriptSerializer();

string t1String = serialiser.Serialize(t);
string t2String = serialiser.Serialize(t2);

if(t1String == t2String)
   return true; //they are equal
else
   return false;

3
它并不总是有效的。JSON 有其自身的限制。依赖于 JSON 来比较一般对象并不是一个好主意。 - liang
尝试过这个,但它不起作用。由于JSON中属性的不同排序,它会导致假负结果。 - NickG

0

一个函数 Equals 不应该总是只测试相同类型,它应该是这样的:

//override
    public bool Equals(Test other)//(object obj) 
    {
        //return GetHashCode() == obj.GetHashCode();
        return (Value == other.Value) &&
               (String1 == other.String1) &&
               (String2 == other.String2);
    }

致意 糟糕


应该重写Object中的方法。你也可以实现IEquatable<Test>,这时你需要指定这个签名。 - Jon Skeet
嗯,不知道,这是VS2010 beta的问题吗?如果我在这里使用override,VS会说:“Test.Equals(Test)”:没有找到适合重写的方法。 - OlimilOops

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