Краткий способ комбинирования полевых хэш-кодов?

Один, если способы реализации GetHashCode - там, где это требуется для этого - описывается Jon Skeet здесь. Повторяя его код:

public override int GetHashCode()
{
    unchecked // Overflow is fine, just wrap
    {
        int hash = 17;
        // Suitable nullity checks etc, of course :)
        hash = hash * 23 + field1.GetHashCode();
        hash = hash * 23 + field2.GetHashCode();
        hash = hash * 23 + field3.GetHashCode();
        return hash;
    }
}

Прокрутка этого кода вручную может быть подвержена ошибкам, а ошибки могут быть тонкими/трудно различимыми (вы поменяли местами + и * по ошибке?), может быть сложно запомнить правила комбинации для разных типов, и мне не нравится тратить умственные усилия на то, чтобы писать и анализировать одно и то же снова и снова для разных полей и классов. Он также может запутать одну из самых важных деталей (я помню, чтобы включать все поля?) В повторяющиеся шумы.

Есть ли сжатый способ комбинировать полевые хэш-коды с использованием библиотеки .net?. Очевидно, я мог бы написать свой собственный, но если есть что-то идиоматическое/встроенное, я бы предпочел это.

В качестве примера, в Java (с использованием JDK7) я могу достичь вышеуказанного, используя:

   @Override
   public int hashCode()  
   {  
      return Objects.hash(field1, field2, field3);  
   }  

Это действительно помогает устранить ошибки и сосредоточиться на важных деталях.

Мотивация: я столкнулся с классом С#, который требует переопределенного GetHashCode(), но способ объединения хэш-кодов его различных составляющих имел некоторые серьезные ошибки. Функция библиотеки для объединения хэш-кодов была бы полезна для предотвращения таких ошибок.

Ответ 1

Некоторые люди используют:

Tuple.Create(lastName, firstName, gender).GetHashCode()

Он упомянул в MSDN в Object.GetHashCode() с предупреждением:

Обратите внимание, однако, что служебные данные производительности для создания экземпляра объекта Tuple могут существенно повлиять на общую производительность приложения, которое хранит большое количество объектов в хэш-таблицах.

Логика агрегирования составляющих хэшей обеспечивается System.Tuple, которая, как мы надеемся, задумалась...

Обновить: стоит отметить замечание @Ryan в комментариях, что это только кажется, что использовать последние 8 элементов любого Tuple of Size > 8.

Ответ 2

Это не совсем то же самое, но у нас есть HashCodeHelper класс в Noda Time (у которого много типов, которые переопределяют операции равенства и хеш-кода).

Используется так (взято из ZonedDateTime):

public override int GetHashCode()
{
    int hash = HashCodeHelper.Initialize();
    hash = HashCodeHelper.Hash(hash, LocalInstant);
    hash = HashCodeHelper.Hash(hash, Offset);
    hash = HashCodeHelper.Hash(hash, Zone);
    return hash;
}

Обратите внимание, что это общий метод, который позволяет избежать бокса для типов значений. Он автоматически обрабатывает нулевые значения (используя значение 0 для значения). Обратите внимание, что метод MakeHash имеет блок unchecked, поскольку Noda Time использует проверочную арифметику в качестве параметра проекта, тогда как вычисления хеш-кода должны быть переполнены.

Ответ 3

РЕДАКТИРОВАТЬ: Оставайтесь с нами, System.HashCode подходит к .NET Core и обеспечит исключительный лучший способ создания хэш-кодов. Он также будет использоваться под капотом System.Tuple и другими неизменяемыми составными типами. Пока он не будет выпущен, ответ ниже будет полезен.

Для полноты, вот алгоритм хэширования, взятый из .NET Tuple Reference source, строка 52. Интересно, что этот хеш алгоритм был скопирован из System.Web.Util.HashCodeCombiner.

Вот код:

public override int GetHashCode() {
    // hashing method taken from .NET Tuple reference
    // expand this out to however many items you need to hash
    return CombineHashCodes(this.item1.GetHashCode(), this.item2.GetHashCode(), this.item3.GetHashCode());
}

internal static int CombineHashCodes(int h1, int h2) {
    // this is where the magic happens
    return (((h1 << 5) + h1) ^ h2);
}

internal static int CombineHashCodes(int h1, int h2, int h3) {
    return CombineHashCodes(CombineHashCodes(h1, h2), h3);
}

internal static int CombineHashCodes(int h1, int h2, int h3, int h4) {
    return CombineHashCodes(CombineHashCodes(h1, h2), CombineHashCodes(h3, h4));
}

internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5) {
    return CombineHashCodes(CombineHashCodes(h1, h2, h3, h4), h5);
}

internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int h6) {
    return CombineHashCodes(CombineHashCodes(h1, h2, h3, h4), CombineHashCodes(h5, h6));
}

internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int h6, int h7) {
    return CombineHashCodes(CombineHashCodes(h1, h2, h3, h4), CombineHashCodes(h5, h6, h7));
}

internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int h6, int h7, int h8) {
    return CombineHashCodes(CombineHashCodes(h1, h2, h3, h4), CombineHashCodes(h5, h6, h7, h8));
}

Конечно, фактический Tuple GetHashCode() (на самом деле Int32 IStructuralEquatable.GetHashCode(IEqualityComparer comparer)) имеет большой блок switch, чтобы решить, какой из них вызывать, исходя из количества элементов, которые он держит - ваш собственный код, вероятно, выиграл 't требуют этого.

Ответ 4

Вот несколько кратких (хотя и не столь эффективных) рефакторов System.Web.Util.HashCodeCombiner, упомянутых в Ryan answer

    public static int CombineHashCodes(params object[] objects)
    {
        // From System.Web.Util.HashCodeCombiner
        int combine(int h1, int h2) => (((h1 << 5) + h1) ^ h2);

        return objects.Select(it => it.GetHashCode()).Aggregate(5381,combine);
    }

    public static int CombineHashCodes(IEqualityComparer comparer, params object[] objects)
    {
        // From System.Web.Util.HashCodeCombiner
        int combine(int h1, int h2) => (((h1 << 5) + h1) ^ h2);

        return objects.Select(comparer.GetHashCode).Aggregate(5381, combine);
    }

Ответ 5

public override GetHashCode()
{
    return this.Field1.GetHashCode() | this.Field2.GetHashCode | this.Field3.GetHashCode();
}