Каков наилучший алгоритм для переопределенного System.Object.GetHashCode?

Это может сработать ...

from p in db.products
    select new
    {
        Owner = (p.price > 0 ?
            from q in db.Users select q.Name :
            from r in db.ExternalUsers select r.Name)
    }
1350
задан iYoung 30 December 2016 в 08:43
поделиться

5 ответов

Я обычно иду с чем-то как реализация, данная в Josh Bloch невероятный Эффективный Java. Это быстро и создает довольно хороший хеш, который вряд ли вызовет коллизии. Выберите два различных простых числа, например, 17 и 23, и сделайте:

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;
    }
}

, Как отмечено в комментариях, можно найти, что лучше выбрать большое начало для умножения вместо этого. По-видимому, 486187739 хорошо... и хотя большинство примеров, которые я видел с небольшими числами, имеет тенденцию использовать начала, существуют, по крайней мере, подобные алгоритмы, где непростые числа часто используются. В не вполне - пример FNV позже, например, я использовал числа, которые, по-видимому, работают хорошо - но начальное значение не является началом. (Умножение, постоянное , главное все же. Я не знаю вполне, как важный, который является.)

Это лучше, чем обычная практика XOR хэш-коды луга по двум главным причинам. Предположим, что у нас есть тип с два int поля:

XorHash(x, x) == XorHash(y, y) == 0 for all x, y
XorHash(x, y) == XorHash(y, x) for all x, y

Между прочим, более ранний алгоритм является тем, в настоящее время используемым компилятором C# для анонимных типов.

Эта страница дает довольно много опций. Я думаю для большей части вышеупомянутого случаев, "достаточно хорошо", и невероятно легко помнить и разобраться. альтернатива FNV столь же проста, но использует различные константы и XOR вместо ADD как операция объединения. Это смотрит что-то как код ниже, но нормальный алгоритм FNV воздействует на отдельные байты, таким образом, это потребовало бы, чтобы изменение выполнило одно повторение на байт, вместо на 32-разрядное значение хэш-функции. FNV также разработан для переменных длин данных, тогда как способ, которым мы используем его здесь, всегда для того же количества значений полей. Комментарии к этому ответу предполагают, что код здесь на самом деле не работает также (в демонстрационном протестированном случае) как дополнительный подход выше.

// Note: Not quite FNV!
public override int GetHashCode()
{
    unchecked // Overflow is fine, just wrap
    {
        int hash = (int) 2166136261;
        // Suitable nullity checks etc, of course :)
        hash = (hash * 16777619) ^ field1.GetHashCode();
        hash = (hash * 16777619) ^ field2.GetHashCode();
        hash = (hash * 16777619) ^ field3.GetHashCode();
        return hash;
    }
}

Примечание, которым одна вещь знать состоит в том, что идеально необходимо предотвратить чувствительное к равенству (и таким образом чувствительный к хэш-коду) состояние от изменения после добавления его к набору, который зависит от хэш-кода.

Согласно документация :

можно переопределить GetHashCode для неизменных ссылочных типов. В целом, для изменяемых ссылочных типов, необходимо переопределить GetHashCode только если:

  • можно вычислить хэш-код из полей, которые не изменяемы; или
  • можно удостовериться, что хэш-код изменяемого объекта не изменяется, в то время как объект содержится в наборе, который полагается на его хэш-код.
1505
ответ дан Cœur 30 December 2016 в 08:43
поделиться

Большая часть моей работы сделана с возможностью соединения базы данных, что означает, что мои классы у всех есть уникальный идентификатор от базы данных. Я всегда использую идентификатор от базы данных для генерации хэш-кода.

// Unique ID from database
private int _id;

...    
{
  return _id.GetHashCode();
}
3
ответ дан Mark G 30 December 2016 в 08:43
поделиться

У меня есть класс Хеширования в библиотеке Helper, что я использую ее с этой целью.

/// <summary> 
/// This is a simple hashing function from Robert Sedgwicks Hashing in C book.
/// Also, some simple optimizations to the algorithm in order to speed up
/// its hashing process have been added. from: www.partow.net
/// </summary>
/// <param name="input">array of objects, parameters combination that you need
/// to get a unique hash code for them</param>
/// <returns>Hash code</returns>
public static int RSHash(params object[] input)
{
    const int b = 378551;
    int a = 63689;
    int hash = 0;

    // If it overflows then just wrap around
    unchecked
    {
        for (int i = 0; i < input.Length; i++)
        {
            if (input[i] != null)
            {
                hash = hash * a + input[i].GetHashCode();
                a = a * b;
            }
        }
    }

    return hash;
}

Затем просто можно использовать его как:

public override int GetHashCode()
{
    return Hashing.RSHash(_field1, _field2, _field3);
}

я не оценил ее производительность, таким образом, любая обратная связь одобрена.

60
ответ дан Mike Chamberlain 9 August 2019 в 06:50
поделиться

В большинстве случаев то, где Равняется () сравнивает несколько полей, действительно не имеет значения, если Ваш GetHash () хеширует на одном поле или на многих. Просто необходимо удостовериться, что вычисление хеша является действительно дешевым ( Никакие выделения ) и быстро ( Никакие тяжелые вычисления и конечно никакие соединения с базой данных) и обеспечивает хорошее распределение.

тяжелый подъем должен быть частью Равняния () метод; хеш должен быть очень дешевой операцией, чтобы позволить звонить, Равняется () на как можно меньшем количестве объектов.

И одна заключительная подсказка: не полагаются на GetHashCode () быть стабильным по нескольким выполнениям приложения . Много типов .NET не гарантируют свои хэш-коды для оставлений такого же после перезапуска, таким образом, необходимо будет только использовать значение GetHashCode () для в структурах данных оперативной памяти.

29
ответ дан Bert Huijben 9 August 2019 в 06:50
поделиться

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

public static class HashHelper
{
    public static int GetHashCode<T1, T2>(T1 arg1, T2 arg2)
    {
         unchecked
         {
             return 31 * arg1.GetHashCode() + arg2.GetHashCode();
         }
    }

    public static int GetHashCode<T1, T2, T3>(T1 arg1, T2 arg2, T3 arg3)
    {
        unchecked
        {
            int hash = arg1.GetHashCode();
            hash = 31 * hash + arg2.GetHashCode();
            return 31 * hash + arg3.GetHashCode();
        }
    }

    public static int GetHashCode<T1, T2, T3, T4>(T1 arg1, T2 arg2, T3 arg3, 
        T4 arg4)
    {
        unchecked
        {
            int hash = arg1.GetHashCode();
            hash = 31 * hash + arg2.GetHashCode();
            hash = 31 * hash + arg3.GetHashCode();
            return 31 * hash + arg4.GetHashCode();
        }
    }

    public static int GetHashCode<T>(T[] list)
    {
        unchecked
        {
            int hash = 0;
            foreach (var item in list)
            {
                hash = 31 * hash + item.GetHashCode();
            }
            return hash;
        }
    }

    public static int GetHashCode<T>(IEnumerable<T> list)
    {
        unchecked
        {
            int hash = 0;
            foreach (var item in list)
            {
                hash = 31 * hash + item.GetHashCode();
            }
            return hash;
        }
    }

    /// <summary>
    /// Gets a hashcode for a collection for that the order of items 
    /// does not matter.
    /// So {1, 2, 3} and {3, 2, 1} will get same hash code.
    /// </summary>
    public static int GetHashCodeForOrderNoMatterCollection<T>(
        IEnumerable<T> list)
    {
        unchecked
        {
            int hash = 0;
            int count = 0;
            foreach (var item in list)
            {
                hash += item.GetHashCode();
                count++;
            }
            return 31 * hash + count.GetHashCode();
        }
    }

    /// <summary>
    /// Alternative way to get a hashcode is to use a fluent 
    /// interface like this:<br />
    /// return 0.CombineHashCode(field1).CombineHashCode(field2).
    ///     CombineHashCode(field3);
    /// </summary>
    public static int CombineHashCode<T>(this int hashCode, T arg)
    {
        unchecked
        {
            return 31 * hashCode + arg.GetHashCode();   
        }
    }

Также он имеет метод расширения для обеспечения свободного интерфейса, поэтому вы можете использовать его следующим образом:

public override int GetHashCode()
{
    return HashHelper.GetHashCode(Manufacturer, PartN, Quantity);
}

или вот так:

public override int GetHashCode()
{
    return 0.CombineHashCode(Manufacturer)
        .CombineHashCode(PartN)
        .CombineHashCode(Quantity);
}
102
ответ дан 19 December 2019 в 20:14
поделиться
Другие вопросы по тегам:

Похожие вопросы: