[英]Why does Visual Studio add “-1937169414” to a generated hash code computation?
如果您使用 Visual Studio 自己的重构菜单将 GetHashCode 实现添加到 class,如下所示:
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 01001
或10100 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.