为这个结构体实现GetHashCode的正确方法
本文关键字:方法 GetHashCode 实现 结构体 | 更新日期: 2023-09-27 18:11:33
我想使用日期范围(从一个日期到另一个日期)作为字典的键,因此我编写了自己的结构体:
struct DateRange
{
public DateTime Start;
public DateTime End;
public DateRange(DateTime start, DateTime end)
{
Start = start.Date;
End = end.Date;
}
public override int GetHashCode()
{
// ???
}
}
实现GetHashCode的最佳方法是什么,这样两个不同范围的对象就不会产生相同的哈希值?我希望哈希冲突尽可能不可能发生,尽管我知道Dictionary<>仍然会检查我也将实现的相等操作符,但不想过多地污染示例代码。谢谢!
您可以使用Effective Java中的方法,如Jon Skeet所示。对于您的特定类型:
public override int GetHashCode()
{
unchecked // Overflow is fine, just wrap
{
int hash = 17;
hash = hash * 23 + Start.GetHashCode();
hash = hash * 23 + End.GetHashCode();
return hash;
}
}
在C# 7
中,您可以这样做:
public override int GetHashCode() => (Start, End).GetHashCode();
ValueTuple
在.NET Framework 4.7
和.NET Core
中可用,或通过NuGet。
不确定它的性能有多好,但如果有任何自定义代码能超过它,我会感到惊讶。
不是为了让死人复活,而是我来这里寻找一些东西,对于较新的c#版本,你可以做
public override int GetHashCode()
{
return HashCode.Combine(Start, End);
}
目前可以在这里找到源代码:https://github.com/dotnet/corert/blob/master/src/System.Private.CoreLib/shared/System/HashCode.cs
在我的初步测试中(使用Jon Skeets的微基准测试框架),在性能方面,它似乎与公认的答案非常相似,如果不是相同的话。
我会相信微软在元组上的GetHashCode()实现,并且使用类似这样的东西而不使用任何愚蠢的魔法:
public override int GetHashCode()
{
Tuple.Create(x, y).GetHashCode();
}
Since DateTime。GetHashCode在内部是基于Ticks的,那么这个呢:
public override int GetHashCode()
{
return unchecked((int)(Start.Ticks ^ End.Ticks));
}
或者,由于您似乎对日期部分(年,月,日)感兴趣,而不是整个事情,因此此实现使用两个日期之间的天数,并且应该几乎没有冲突:
public override int GetHashCode()
{
return unchecked((int)Start.Date.Year * 366 + Start.Date.DayOfYear + (End.Date - Start.Date).Days);
}
因为它可能会帮助将来使用visual studio pro的人(不确定是否也存在于社区版中)
- 选择所需的属性(在您的情况下全部)
- 按重构(CTRL +)。或右键单击"快速操作和重构"
- 现在你可以选择实现Equals或GetHashcode(可能它总是采取最著名的MS方式来做它)
像这样:)用不同的素数:)
public override int GetHashCode()
{
unchecked
{
int hash = 23;
// Suitable nullity checks etc, of course :)
hash = hash * 31 + Start.GetHashCode();
hash = hash * 31 + End.GetHashCode();
return hash;
}
}
这不是最快的实现,但它产生了一个很好的哈希代码。Joshua bloch也指出,你也可以计算性能,^通常更快。如果我说错了请指正。
参见Jon Skeets的示例c#:
结合Jon Skeet对这个问题的回答和评论(所以请不要对这个问题投票,只是合并):
struct DateRange
{
private readonly DateTime start;
private readonly DateTime end;
public DateRange(DateTime start, DateTime end)
{
this.start = start.Date;
this.end = end.Date;
}
public DateTime Start
{
get
{
return this.start;
}
}
public DateTime End
{
get
{
return this.end;
}
}
public static bool operator ==(DateRange dateRange1, DateRange dateRange2)
{
return dateRange1.Equals(dateRange2);
}
public static bool operator !=(DateRange dateRange1, DateRange dateRange2)
{
return !dateRange1.Equals(dateRange2);
}
public override int GetHashCode()
{
// Overflow is fine, just wrap
unchecked
{
var hash = 17;
// Suitable nullity checks etc, of course :)
hash = (23 * hash) + this.start.GetHashCode();
hash = (23 * hash) + this.end.GetHashCode();
return hash;
}
}
public override bool Equals(object obj)
{
return (obj is DateRange)
&& this.start.Equals(((DateRange)obj).Start)
&& this.end.Equals(((DateRange)obj).End);
}
}