Почему Visual Studio добавляет "-1937169414" к сгенерированному вычислению кода ha sh? - PullRequest
9 голосов
/ 30 апреля 2020

Если вы используете собственное меню рефакторинга Visual Studio, чтобы добавить реализацию GetHashCode к классу, подобному следующему:

Generate GetHashCode menu

и выберите единственное свойство int в класс:

Member selection screen

генерирует этот код на. NET Framework:

public override int GetHashCode()
{
    return -1937169414 + Value.GetHashCode();
}

(генерирует HashCode.Combine(Value) NET Core вместо этого, но я не уверен, что оно имеет одинаковое значение)

Что особенного в этом значении? Почему Visual Studio не использует Value.GetHashCode() напрямую? Как я понимаю, это действительно не влияет на распределение sh. Поскольку это всего лишь дополнение, последовательные значения все равно будут накапливаться вместе.

РЕДАКТИРОВАТЬ: я пробовал это только с различными классами со свойствами Value, но, очевидно, имя свойства влияет на генерируемое число. Например, если вы переименуете свойство в Halue, число станет 387336856. Спасибо Гекхану Курту, который указал на это.

Ответы [ 2 ]

3 голосов
/ 30 апреля 2020

Как пояснил в комментариях GökhanKurt , число изменяется в зависимости от названий свойств. Если вы переименуете свойство в Halue, вместо этого число станет 387336856. Я пробовал это с разными классами, но не думал о переименовании свойства.

Комментарий Гекхана заставил меня понять его назначение. Это смещение значений ha sh, основанное на детерминированности c, но случайно распределенном смещении. Таким образом, объединение значений ha sh для разных классов, даже с простым добавлением, все еще немного устойчиво к коллизиям ha sh.

Например, если у вас есть два класса с похожими реализациями GetHashCode:

public class A
{
    public int Value { get; set;}
    public int GetHashCode() => Value;
}

public class B
{
    public int Value { get; set;}
    public override int GetHashCode() => Value;
}

и если у вас есть другой класс, который содержит ссылки на эти два:

public class C
{
    public A ValueA { get; set; }
    public B ValueB { get; set; }
    public override int GetHashCode()
    {
        return ValueA.GetHashCode() + ValueB.GetHashCode();
    }
}

плохая комбинация, подобная этой, будет склонна к коллизиям ha sh, потому что в результате ха * Код 1024 * будет накапливаться вокруг одной и той же области для разных значений ValueA и ValueB, если их значения близки друг к другу. На самом деле не имеет значения, используете ли вы умножение или побитовые операции для их объединения, они все равно будут подвержены коллизиям без равномерно удаленного смещения. Поскольку многие целочисленные значения, используемые в программировании, накапливаются около 0, имеет смысл использовать такое смещение

По-видимому, хорошей практикой является случайное смещение с хорошими битовыми комбинациями.

I ' Я до сих пор не уверен, почему они не используют совершенно случайные смещения, возможно, чтобы не нарушать код, основанный на детерминизме GetHashCode (), но было бы здорово получить комментарий от команды Visual Studio по этому поводу.

2 голосов
/ 01 мая 2020

Если вы посмотрите на -1521134295 в репозиториях Microsoft, вы увидите, что оно появляется довольно часто

Большинство результатов поиска представлены в функциях GetHashCode, но все они имеют следующую форму

int hashCode = SOME_CONSTANT;
hashCode = hashCode * -1521134295 + field1.GetHashCode();
hashCode = hashCode * -1521134295 + field2.GetHashCode();
// ...
return hashCode;

Первый hashCode * -1521134295 = SOME_CONSTANT * -1521134295 будет предварительно умножено во время генерации на генератор или во время компиляции на CS C. Вот почему -1937169414 в вашем коде

При более глубоком рассмотрении результатов обнаруживается часть генерации кода, которую можно найти в функции CreateGetHashCodeMethodStatements

const int hashFactor = -1521134295;

var initHash = 0;
var baseHashCode = GetBaseGetHashCodeMethod(containingType);
if (baseHashCode != null)
{
    initHash = initHash * hashFactor + Hash.GetFNVHashCode(baseHashCode.Name);
}

foreach (var symbol in members)
{
    initHash = initHash * hashFactor + Hash.GetFNVHashCode(symbol.Name);
}

As Вы можете видеть, что ха sh зависит от имен символов. В этой функции константа также называется permuteValue, вероятно потому, что после умножения биты переставляются как-то

// -1521134295
var permuteValue = CreateLiteralExpression(factory, hashFactor);

Существуют некоторые шаблоны, если мы рассматриваем значение в двоичном виде: 101001 010101010101010 101001 01001 или 10100 1010101010101010 10100 10100 1. Но если мы умножим произвольное значение на это, тогда будет много перекрывающихся переносов, поэтому я не могу понять, как это работает. Выход также может иметь различное количество установленных битов, так что на самом деле это не перестановка

Вы можете найти другой генератор в AnonymousTypeGetHashCodeMethodSymbol , который вызывает константу HASH_FACTOR

//  Method body:
//
//  HASH_FACTOR = 0xa5555529;
//  INIT_HASH = (...((0 * HASH_FACTOR) + GetFNVHashCode(backingFld_1.Name)) * HASH_FACTOR
//                                     + GetFNVHashCode(backingFld_2.Name)) * HASH_FACTOR
//                                     + ...
//                                     + GetFNVHashCode(backingFld_N.Name)

Реальная причина выбора этого значения пока неясна

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...