Neo*_*Neo 8 c# mutable immutability iequatable gethashcode
我正在实施IEquatable<T>,但我很难就可变GetHashCode类的覆盖达成共识。
以下资源都提供了一个实现,GetHashCode如果对象发生更改,则在对象的生命周期内将返回不同的值:
但是,此链接指出GetHashCode不应为可变类型实现,因为如果对象是集合的一部分,则可能会导致不良行为(这也一直是我的理解)。
有趣的是,MSDN 示例实现了GetHashCode仅使用不可变属性,这符合我的理解。但我很困惑为什么其他资源不涵盖这一点。他们真的错了吗?
如果一个类型根本没有不可变属性,编译器会GetHashCode在我重写时警告该类型丢失Equals(object)。在这种情况下,我应该实现它并只调用base.GetHashCode()或禁用编译器警告,还是我错过了某些内容并且GetHashCode应该始终被覆盖和实现?事实上,如果建议不GetHashCode应该为可变类型实现,为什么还要为不可变类型实现呢?与默认实现相比,它只是为了减少冲突GetHashCode,还是实际上添加了更多有形的功能?
总结我的问题,我的困境是,GetHashCode在可变对象上使用意味着如果对象的属性发生变化,它可以在对象的生命周期内返回不同的值。但不使用它意味着比较可能等效的对象的好处会丢失,因为它将始终返回唯一值,因此集合将始终回退到使用Equals其操作。
输入此问题后,“类似问题”框中弹出了另一个问题,似乎涉及同一主题。答案似乎非常明确,因为在GetHashCode实现中只应使用不可变属性。如果没有,那就干脆不写。Dictionary<TKey, TValue>尽管不是 O(1) 性能,但仍然可以正常运行。
可变类与字典和其他依赖 GetHashCode 和 Equals 的类一起工作非常糟糕。
在您描述的场景中,对于可变对象,我建议采用以下方法之一:
class ConstantHasCode: IEquatable<ConstantHasCode>
{
public int SomeVariable;
public virtual Equals(ConstantHasCode other)
{
return other.SomeVariable == SomeVariable;
}
public override int GetHashCode()
{
return 0;
}
}
Run Code Online (Sandbox Code Playgroud)
或者
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");
}
}
Run Code Online (Sandbox Code Playgroud)
对于第一种情况,Dictionary(几乎)按预期工作,但在查找和插入方面存在性能损失:在这两种情况下,都会为字典中已有的每个元素调用 Equals,直到比较返回 true。您实际上正在恢复列表的性能
第二种是告诉程序员将使用您的类“不,您不能在字典中使用它”的方法。不幸的是,据我所知,没有方法可以在编译时检测到它,但是当代码第一次向字典添加元素时,这会失败,很可能是在开发过程中很早的时候,而不是只在生产中发生的那种错误具有不可预测的输入集的环境。
最后但并非最不重要的一点是,忽略“可变”问题并使用成员变量实现 GetHashCode:现在您必须意识到,当与字典一起使用时,您不能随意修改该类。在某些情况下这是可以接受的,但在其他情况下则不然