我是否正确实现了Equals()/GetHashCode()方法?

8
该程序使用以下实现方式:
class Instrument
{
    public string ClassCode { get; set; }
    public string Ticker { get; set; }
    public override string ToString()
    {
        return " ClassCode: " + ClassCode + " Ticker: " + Ticker + '.';
    }
}

但是由于我需要在字典中使用Instrument,所以我决定实现equals/hashcode:

class Instrument
{
    public string ClassCode { get; set; }
    public string Ticker { get; set; }
    public override string ToString()
    {
        return " ClassCode: " + ClassCode + " Ticker: " + Ticker + '.';
    }

    public override bool Equals(object obj)
    {
        if (obj == null)
            return false;

        Instrument instrument = obj as Instrument;
        if (instrument == null)
            return false;

        return ((ClassCode.Equals(instrument.ClassCode)) && (Ticker.Equals(instrument.Ticker));
    }

    public override int GetHashCode()
    {
        int hash = 13;
        hash = (hash * 7) + ClassCode.GetHashCode();
        hash = (hash * 7) + Ticker.GetHashCode();
        return hash;
    }
}

现在程序已经停止工作。在这样或类似的地方,我会收到“KeyNotFoundException”:

if (cache.Keys.Any(instrument => instrument.Ticker == newTicker && instrument.ClassCode == newClassCode))

有没有可能有些代码片段假定equals和hashcode方法没有被实现?或者我只是实现它们的方式不正确?抱歉,我对C#中的这些高级特性不熟悉,也不知道最后一段代码与equals或hashCode有什么关联。


11
简而言之,即使实例发生变化,您的哈希码也应保持不变,因此您所做的任何实现都应以此为首要目标。请参阅Eric Lippert在以下链接中对此问题的阐述:http://blogs.msdn.com/b/ericlippert/archive/2011/02/28/guidelines-and-rules-for-gethashcode.aspx。 - jjrdk
目前我在外出,所以我只会提供一个我发现对于“equals”部分有用的链接... http://geekswithblogs.net/akraus1/archive/2010/02/28/138234.aspx - cyberzed
3个回答

7

您的HashCode和Equals方法应该只依赖于不可变属性-您的实现使用了ClassCode和Ticker,它们都有setter因此是可变的。


谢谢Joe,迄今为止我还没有仔细阅读过C#为何有如此奇怪的限制。今晚我会阅读建议的文章。然而,到目前为止它听起来非常奇怪。如果某个对象等于另一个对象,然后更改了一个对象的属性,我希望这些对象不再相等!因此,“equals”必须包括所有属性,特别是可变属性......这就是我在Java中做事情的方式 :) - Oleg Vazhnev
5
假设你将一个对象放入字典中,它将基于其计算出的哈希值被分配到一个桶中,假设它被分配到桶#1中。现在,你修改了刚刚放入字典中的对象中的Ticker和/或ClassCode,并使用.Contains进行查找。字典会调用GetHashCode来确定应该搜索哪个桶以查找我们的对象 - 由于修改后的值,它认为应该是#2而不是#1。它检查桶#2 - 但是你的对象不在那里。.Contains返回false,对于刚刚放入字典中的对象。这就是为什么你需要使用不可变成员进行哈希生成的原因。 - k.m
@jimmy_keen,非常好的观点。所以我猜测字典不会接收其键的OnPropertyChanged通知,或者类似的情况。因此,只有在添加时才计算密钥哈希值。我想知道为什么我从未看到过关于这方面的文章。 - HuBeZa
1
@jimmy_keen 我认为这是一个有价值的功能,而不是一个危险 :) - nawfal

3

首先,不需要使用cache.Keys.Any,你可以直接使用ContainsKey方法。

bool contains = cache.ContainsKey(
    new Instrument { Ticker = newTicker, ClassCode = newClassCode });

第一个迭代遍历整个键列表 - O(n),而第二个使用字典内置的哈希表实现 - O(1)。

其次,在您的实现中检查空引用:

public override bool Equals(object obj)
{
    if (obj == null)
        return false;

    Instrument instrument = obj as Instrument;
    if (instrument == null)
        return false;

    // 1. string.Equals can handle null references.
    // 2. object.ReferenceEquals for better preformances when it's the same object
    return (object.ReferenceEquals(this, instrument)) ||
        (string.Equals(ClassCode, instrument.ClassCode) &&
        string.Equals(Ticker, instrument.Ticker));
}

public override int GetHashCode()
{
    int hash = 13;
    if (ClassCode != null)
        hash = (hash * 7) + ClassCode.GetHashCode();
    if (Ticker!= null)
        hash = (hash * 7) + Ticker.GetHashCode();

    return hash;
}

除此之外,我看不出有什么问题。


关于“null”引用,我的Instrument对象不能包含“null”字符串是有意设计的,因此如果某个字段意外为空,我实际上更希望引发NPE。我认为代码使用cache.Keys.Any是因为没有实现equals和hashCode(那么我想ContainsKey将不起作用)。问题是如何打破cache.Keys.Any实现Equals/HashCode... - Oleg Vazhnev

1
但是因为我需要在字典中使用仪器,所以我决定实现equals/hashcode。
这是错误的原因。你的类已经有了适合、高效、经过测试的等式和gethashcode实现,可以在字典中使用。
我实现了equals()/gethashcode(),是吗?
不是。首先,你缺少一个==的重载。只有当你使乐器不变时,它才会可靠。
你最好的做法是不要覆盖任何这些成员。
此外,请参见this MSDN建议。请注意“equals的保证”,以及
在非不变类型中覆盖操作员==是不推荐的。

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