Я пытаюсь использовать некоторый код, который будет генерировать хэш-код на основе значения всех свойств внутри объекта, но следующее возвращает 0 для HashCodeOnProperties
Console.WriteLine("Hello, World!");
var request = new Request()
{
NorthEastLatitude = 43.13306116240615,
NorthEastLongitude = -80.9355926513672,
NorthWestLatitude = 43.13306116240615,
NorthWestLongitude = -81.573486328125014,
SouthEastLatitude = 42.831667202614092,
SouthEastLongitude = -80.9355926513672 ,
SouthWestLatitude = 42.831667202614092,
SouthWestLongitude = -81.573486328125014
};
var hash = request.GetHashCodeOnProperties();
Console.WriteLine(hash);
Console.ReadKey();
public class Request
{
public double? SouthWestLatitude { get; set; }
public double? SouthWestLongitude { get; set; }
public double? NorthEastLatitude { get; set; }
public double? NorthEastLongitude { get; set; }
public double? SouthEastLatitude { get; set; }
public double? SouthEastLongitude { get; set; }
public double? NorthWestLatitude { get; set; }
public double? NorthWestLongitude { get; set; }
}
public static class HashCodeByPropertyExtensions
{
public static int GetHashCodeOnProperties<T>(this T inspect)
{
return inspect.GetType().GetProperties().Select(o => o.GetValue(inspect)).GetListHashCode();
}
public static int GetListHashCode<T>(this IEnumerable<T> sequence)
{
return sequence
.Where(item => item != null)
.Select(item => item.GetHashCode())
.Aggregate((total, nextCode) => total ^ nextCode);
}
}
Но, по сути, ваш комбинированный хеш-код означает, что если вы получите один и тот же хеш-код для двух свойств, они будут аннулировать друг друга (из-за XOR). Теперь посмотрите на значения, которые вы указали в своем объекте Request...
Рассмотрите возможность использования HashCode для таких сценариев; HashCode.Combine
хорошо сочетается с .Aggregate
. Вы также захотите взглянуть на record
s, так как они предоставляют реализацию хеш-кода «бесплатно».
Рассмотрите возможность использования структуры HashCode (если вы не используете .NET Framework). [Редактировать: Ах, Йерун Мостерт написал это, пока я печатал.] Но все же интересно услышать объяснение. Но, как говорит Джон Скит, это зависит от ценности. Одна вещь, которую я вижу, это то, что перегрузка Aggregate
, которую вы используете, вызовет исключение, если все свойства имеют нулевые значения. Это включает, конечно, случай, когда свойства отсутствуют.
кстати, записи переопределяют GetHashCode()
и дадут вам эту функциональность из коробки: learn.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/…
Вы связываете значения без «смещения», так что это приведет к тому, что одни и те же значения будут отменять друг друга (исключающее или является ассоциативным и коммутативным, поэтому 42 ^ 77 ^ 42 ^ 77 эквивалентно 42 ^ 42 ^ 77 ^ 77, что равно явно 0). Вы можете сделать что-то вроде:
public static int GetListHashCode<T>(this IEnumerable<T> sequence)
{
var hash = 17;
return sequence
.Where(item => item != null)
.Select(item => item.GetHashCode())
.Aggregate(hash, (total, nextCode) => unchecked(total*23 + nextCode));
}
Или используйте System.HashCode (доступно начиная с .NET Core 2.1) для выполнения агрегации:
public static int GetListHashCode<T>(this IEnumerable<T> sequence)
{
var hashCode = new HashCode();
return sequence
.Where(item => item != null)
.Select(item => item.GetHashCode())
.Aggregate(new HashCode(), (code, i) =>
{
hashCode.Add(i);
return hashCode;
})
.ToHashCode();
}
P.S.
Вычисление хэш-кода должно быть быстрой операцией, в то время как отражение обычно не очень быстрый подход. Подумайте об использовании генераторов исходного кода или какой-либо динамической компиляции кода с деревьями выражений (см., например, это или это ответы для вдохновения) или переключитесь на использование record
, в которых элементы равенства генерируются автоматически.
Вы не используете hash = 17
в первом примере.
@JeppeStigNielsen спасибо, обновлял ответ, теперь он исправлен.
Просто комментарий с картинками. Если ваш класс Request
должен всегда представлять кольцевой сектор Земли, границы которого следуют линиям долготы и широты, то вам нужно хранить координаты только для двух диаметрально противоположных углов.
У @GuruStron есть правильный ответ, я просто хочу включить более надежную функцию совокупного хеш-кода, основанную на коде, сгенерированном VS.
public static int GetListHashCode<T>(this IEnumerable<T> sequence)
{
unchecked
{
return sequence.Where(item => item != null)
.Aggregate(-1817952719,
(hc, item) => (-1521134295) * hc + item.GetHashCode());
}
}
Значения -1817952719
и -1521134295
— это просто большие причудливые числа, которыми можно заменить 17
и 23
, чтобы уменьшить изменения при столкновении. Также необходимо заключить операцию в unchecked
, чтобы избежать любых исключений целочисленного переполнения.
--
Я получил значения из предложенного кода, когда запускаю команду generate GetHashCode() в Visual Studio.
Числа 1817952719
и 1521134295
не являются простыми в строгом математическом смысле, но поскольку они взаимно просты с Pow(2, 32)
, они достаточно хороши.
@JeppeStigNielsen - важен отрицательный знак. Приведите к unsigned int, чтобы получить фактическое число, используемое внутри. Они превращаются в 2477014577
и 2773833001
. Но увы, они тоже не простые.
Но -1817952719 + Pow(2, 32)
также не является математическим простым числом (будучи 16363 * 151379
), так почему люди называют такие числа «простыми»?
@JeppeStigNielsen - хорошо, я обновил свой ответ, больше не называя их простыми числами. Спасибо.
Ну, вы отладили код, чтобы увидеть, что происходит? (Даже простое добавление
.Select(hash => { Console.WriteLine($"Partial hash: {hash}"); return hash; }
даст вам больше информации...)