Почему этот генерирующий хеш равен 0?

Я пытаюсь использовать некоторый код, который будет генерировать хэш-код на основе значения всех свойств внутри объекта, но следующее возвращает 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);
    }
}

Ну, вы отладили код, чтобы увидеть, что происходит? (Даже простое добавление .Select(hash => { Console.WriteLine($"Partial hash: {hash}"); return hash; } даст вам больше информации...)

Jon Skeet 13.04.2023 13:10

Но, по сути, ваш комбинированный хеш-код означает, что если вы получите один и тот же хеш-код для двух свойств, они будут аннулировать друг друга (из-за XOR). Теперь посмотрите на значения, которые вы указали в своем объекте Request...

Jon Skeet 13.04.2023 13:11

Рассмотрите возможность использования HashCode для таких сценариев; HashCode.Combine хорошо сочетается с .Aggregate. Вы также захотите взглянуть на records, так как они предоставляют реализацию хеш-кода «бесплатно».

Jeroen Mostert 13.04.2023 13:27

Рассмотрите возможность использования структуры HashCode (если вы не используете .NET Framework). [Редактировать: Ах, Йерун Мостерт написал это, пока я печатал.] Но все же интересно услышать объяснение. Но, как говорит Джон Скит, это зависит от ценности. Одна вещь, которую я вижу, это то, что перегрузка Aggregate, которую вы используете, вызовет исключение, если все свойства имеют нулевые значения. Это включает, конечно, случай, когда свойства отсутствуют.

Jeppe Stig Nielsen 13.04.2023 13:27

кстати, записи переопределяют GetHashCode() и дадут вам эту функциональность из коробки: learn.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/‌​…

Christoph Lütjen 13.04.2023 13:33
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
5
138
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Вы связываете значения без «смещения», так что это приведет к тому, что одни и те же значения будут отменять друг друга (исключающее или является ассоциативным и коммутативным, поэтому 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 в первом примере.

Jeppe Stig Nielsen 13.04.2023 13:33

@JeppeStigNielsen спасибо, обновлял ответ, теперь он исправлен.

Guru Stron 13.04.2023 13:36

Просто комментарий с картинками. Если ваш класс 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), они достаточно хороши.

Jeppe Stig Nielsen 13.04.2023 13:58

@JeppeStigNielsen - важен отрицательный знак. Приведите к unsigned int, чтобы получить фактическое число, используемое внутри. Они превращаются в 2477014577 и 2773833001. Но увы, они тоже не простые.

John Alexiou 13.04.2023 14:02

Но -1817952719 + Pow(2, 32) также не является математическим простым числом (будучи 16363 * 151379), так почему люди называют такие числа «простыми»?

Jeppe Stig Nielsen 13.04.2023 14:06

@JeppeStigNielsen - хорошо, я обновил свой ответ, больше не называя их простыми числами. Спасибо.

John Alexiou 13.04.2023 14:09

Другие вопросы по теме