应该在可变类型的IEquatable <t>上实现GetHashCode吗?

时间:2018-03-01 17:43:05

标签: c# immutability mutable gethashcode iequatable

我正在实施IEquatable<T>,而我很难就可变类的GetHashCode覆盖找到共识。

以下资源都提供了一种实现,如果对象发生更改,GetHashCode将在对象的生命周期内返回不同的值:

但是,this link声明GetHashCode应该为可变类型实现,因为如果对象是集合的一部分,它可能会导致不良行为(和这一直是我的理解。)

有趣的是,MSDN example仅使用不可变属性来实现GetHashCode,这符合我的理解。但我很困惑为什么其他资源不能涵盖这一点。他们完全错了吗?

如果类型根本没有不可变属性,编译器会在覆盖GetHashCode时警告Equals(object)丢失。在这种情况下,我应该实现它,只是调用base.GetHashCode()或只是禁用编译器警告,或者我错过了什么,GetHashCode应该总是被覆盖并实现?实际上,如果建议不应该为可变类型实现GetHashCode,为什么还要为不可变类型实现呢?与默认的GetHashCode实现相比,它只是简单地减少冲突,还是实际上增加了更多有形的功能?

总结我的问题,我的困境是在可变对象上使用GetHashCode意味着如果对象的属性发生变化,它可以在对象的生命周期内返回不同的值。但是不使用它意味着比较可能等效的对象的好处会丢失,因为它总是返回一个唯一值,因此集合将总是回归到使用Equals进行操作。

输入此问题后,another Question会出现在“类似问题”中。似乎解决相同主题的框。答案似乎非常明确,因为在GetHashCode实现中只应使用不可变属性。如果没有,那么就不要写一个。 Dictionary<TKey, TValue>仍将正常运行,尽管不是O(1)表现。

3 个答案:

答案 0 :(得分:0)

这完全取决于您所谈论的collection类型。对于我的回答,我将假设您正在讨论基于Hash Table的{​​{1}},特别是我将针对.NET collectionsDictionary计算它。

因此,如果您修改Key(鉴于您的key是一个进行自定义key计算的类),那么确定将会发生什么的最佳方法是查看.NET源代码。从.NET源代码我们可以看到,您的HashCode现已包含在key value pair结构中,该Entry结构带有hashcode,该addition是根据您的值HashCode计算的。这意味着如果您在添加密钥后更改dictionary值,则无法再在 static void Main() { var myKey = new MyKey { MyBusinessKey = "Ohai" }; var dic = new Dictionary<MyKey, int>(); dic.Add(myKey, 1); Console.WriteLine(dic[myKey]); myKey.MyBusinessKey = "Changing value"; Console.WriteLine(dic[myKey]); // Key Not Found Exception. } public class MyKey { public string MyBusinessKey { get; set; } public override int GetHashCode() { return MyBusinessKey.GetHashCode(); } } 中找到值。

证明它的代码:

hashcode

.NET source reference.

所以回答你的问题。您希望拥有基于hashcode计算的不可变值。

如果您不覆盖GetHashCode,自定义类的另一点object将基于hashcode的引用。因此,可以通过overriding GetHashCode方法减少对基础值相同的不同对象返回相同HashCode的问题,并根据您的业务键计算concat。例如,您将有两个字符串属性,用于计算哈希码,strings string和调用基础GetHashCode hashcode方法。这将保证您对object的相同基础值获得相同的 class TransactionsViewConttroller : MyBaseTableViewController{ override func viewDidLoad() { super.viewDidLoad() self.refreshControl?.addTarget(self, action: #selector(refresh), for: .valueChanged) } @objc func refresh() { justRefreshTransactions() } private func justRefreshTransactions(){ ServiceRequest.getLatestTransactions(completionBlock : {(error, errorMsg,result) in self.stopRefrshIndic() }) } private func stopRefrshIndic(){ DispatchQueue.main.async { self.tableView.beginUpdates() self.refreshControl?.endRefreshing() self.tableView.endUpdates() } }

答案 1 :(得分:0)

对于依赖于GetHashCode和Equals的字典和其他类,可变类的工作非常糟糕。

在您描述的场景中,对于可变对象,我建议使用以下方法之一:

class ConstantHasCode: IEquatable<ConstantHasCode>
{
    public int SomeVariable;
    public virtual Equals(ConstantHasCode other)
    {
        return other.SomeVariable == SomeVariable;
    }

    public override int GetHashCode()
    {
        return 0;
    }
}

class ThrowHasCode: IEquatable<ThrowHasCode>
{
    public int SomeVariable;
    public virtual Equals(ThrowHasCode other)
    {
        return other.SomeVariable == SomeVariable;
    }

    public override int GetHashCode()
    {
        throw new ApplicationException("this class does not support GetHashCode and should not be used as a key for a dictionary");
    }
}

使用第一个,Dictionary(几乎)按预期工作,在查找和插入时性能下降:在这两种情况下,将为字典中已有的每个元素调用Equals,直到比较返回true。您实际上正在恢复列表的性能

第二种方法是告诉程序员将使用你的类#34;不,你不能在字典中使用它&#34;。 不幸的是,据我所知,在编译时没有方法可以检测到它,但是这会在代码第一次向字典添加元素时失败,很可能在开发时很早,而不是仅在生产中发生的那种错误环境中有一组不可预测的输入。

最后但并非最不重要的是,忽略&#34; mutable&#34;问题并使用成员变量实现GetHashCode:现在您必须知道,当它与字典一起使用时,您无法自由修改该类。在某些情况下,这是可以接受的,而在其他情况下则不是

答案 2 :(得分:-2)

经过多次讨论并阅读关于该主题的其他SO答案后,最终this ReSharper help page对我进行了很好的总结:

   GetHashCode()方法的

MSDN documentation并未明确要求您重写此方法会返回在对象生命周期内永不更改的值。具体来说,它说:

     

对象的GetHashCode方法必须始终返回相同的哈希码,只要不对对象状态进行修改即可确定对象的Equals方法的返回值。

     

另一方面,它表示哈希代码至少在对象位于集合中时不应更改:

     

*您可以为不可变引用类型覆盖GetHashCode。通常,对于可变引用类型,只有在以下情况下才应覆盖GetHashCode:

     
      
  • 您可以从不可变的字段计算哈希码;或
  •   
  • 当对象包含在依赖于其哈希码的集合中时,您可以确保可变对象的哈希码不会更改。*
  •   
     

但为什么你首先需要覆盖 GetHashCode()?通常,如果您的对象将用于 Hashtable ,作为字典中的键等,您将会这样做,并且很难预测何时将对象添加到集合中它将保留多长时间。

     

尽管如此,如果您想要安全起见,请确保在对象的生命周期内覆盖 GetHashCode()返回相同的值。 ReSharper将通过指向 GetHashCode()实现中的每个非readonly字段或非get-only属性来帮助您。如果可能,ReSharper还会建议quick-fixes使这些成员成为只读/只使用。

当然,如果不能进行快速修复,它并不建议该怎么办。但是,它确实表明这些快速修复只应“在可能的情况下”使用,这意味着可以抑制检查。 Gian Paolo对此的回答建议抛出一个例外,这个例外将阻止该类被用作关键,并且如果它无意中被用作关键,它将在开发早期出现。

但是,GetHashCode用于其他情况,例如将对象的实例作为参数传递给模拟方法设置。因此,唯一可行的选择是使用可变值实现GetHashCode并将责任放在代码的其余部分上以确保对象在被用作键时不会发生变异,或者不将其用作一把钥匙。