簡體   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