Правильный способ реализации GetHashCode для этой структуры
Я хочу использовать диапазон дат (от одной даты до другой даты) в качестве ключа для словаря, поэтому я написал свою собственную структуру:
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 < > все равно проверит оператор равенства, который я также буду реализовывать, но не хотел слишком сильно загрязнять код примера. Спасибо!
Ответы
Ответ 1
Вы можете использовать этот метод из 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;
}
}
Ответ 2
Я бы доверял Microsoft реализацию GetHashCode() в кортежах и использовал что-то вроде этого без какой-либо глупой магии:
public override int GetHashCode()
{
Tuple.Create(x, y).GetHashCode();
}
Ответ 3
Так как 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);
}
Ответ 4
Что-то вроде этого:) с другим простым числом:)
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 impl для С#:
Ответ 5
В C# 7
вы можете сделать это:
public override int GetHashCode() => (Start, End).GetHashCode();
ValueTuple
доступен в .NET Framework 4.7
и .NET Core
или через NuGet.
Не уверен, насколько хорошо он работает, но я был бы удивлен, если бы какой-то пользовательский код побил его.
Ответ 6
Сочетание 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);
}
}