繁体   English   中英

为什么 Visual Studio 会在生成的 hash 代码计算中添加“-1937169414”?

[英]Why does Visual Studio add “-1937169414” to a generated hash code computation?

如果您使用 Visual Studio 自己的重构菜单将 GetHashCode 实现添加到 class,如下所示:

生成 GetHashCode 菜单

select 是 class 中唯一的 int 属性:

成员选择画面

它在 .NET 框架上生成此代码:

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

(它在 .NET 核心上生成HashCode.Combine(Value) ,我不确定它是否涉及相同的值)

这个值有什么特别之处? 为什么 Visual Studio 不直接使用Value.GetHashCode() 据我了解,它并没有真正影响 hash 分布。 由于它只是加法,连续的值仍然会累积在一起。

编辑:我只尝试了具有Value属性的不同类,但显然属性名称会影响生成的数字。 例如,如果您将属性重命名为Halue ,则数字变为 387336856。感谢 Gökhan Kurt 指出这一点。

正如GökhanKurt在评论中解释的那样,数字会根据所涉及的属性名称而变化。 如果您将该属性重命名为Halue ,则数字变为 387336856。 我曾尝试过使用不同的类,但没有考虑重命名该属性。

Gökhan 的评论让我明白了它的目的。 它基于确定性但随机分布的偏移量偏移 hash 值。 这样,组合不同类的 hash 值,即使是简单的相加,仍然对 hash 碰撞有一定的抵抗力。

例如,如果您有两个具有类似 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;
}

如果您有另一个 class 包含对这两个的引用:

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

像这样的糟糕组合很容易发生 hash 冲突,因为如果 ValueA 和 ValueB 的值彼此接近,则生成的 hash 代码将在相同区域附近累积。 如果您使用乘法或按位运算来组合它们真的没关系,如果没有均匀距离的偏移,它们仍然容易发生冲突。 由于编程中使用的许多 integer 值累积在 0 左右,因此使用这样的偏移量是有意义的

显然,具有良好位模式的随机偏移量是一个好习惯。

我仍然不确定他们为什么不使用完全随机的偏移量,可能不会破坏任何依赖于 GetHashCode() 确定性的代码,但很高兴收到 Visual Studio 团队对此的评论。

如果您在 Microsoft 的存储库中查找-1521134295 ,您会看到它出现了很多次

大部分搜索结果都在GetHashCode函数中,但都具有以下形式

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

第一个hashCode * -1521134295 = SOME_CONSTANT * -1521134295将在生成期间由生成器或在编译期间由 CSC 进行预乘。 这就是您的代码中-1937169414的原因

深入研究结果揭示了代码生成部分,可以在 function 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);
}

如您所见,hash 取决于符号名称。 在那个 function 中,常数也称为permuteValue ,可能是因为在乘法之后,位以某种方式排列

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

如果我们以二进制形式查看值,则会出现一些模式: 101001 010101010101010 101001 0100110100 1010101010101010 10100 10100 1 但是如果我们将一个任意值乘以它,那么就会有很多重叠的进位,所以我看不出它是如何工作的。 output 也可能有不同数量的设置位,所以它不是真正的排列

您可以在 Roslyn 的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)

选择该值的真正原因尚不清楚

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM