// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // Taken from https://raw.githubusercontent.com/CommunityToolkit/dotnet/ecd1711b740f4f88d2bb943ce292ae4fc90df1bc/src/CommunityToolkit.Mvvm.SourceGenerators/Helpers/HashCode.cs using System.ComponentModel; using System.Runtime.CompilerServices; using System.Security.Cryptography; #pragma warning disable CS0809 namespace System; #nullable enable /// /// A polyfill type that mirrors some methods from on .NET 6. /// public struct HashCode { private const uint Prime1 = 2654435761U; private const uint Prime2 = 2246822519U; private const uint Prime3 = 3266489917U; private const uint Prime4 = 668265263U; private const uint Prime5 = 374761393U; private static readonly uint seed = GenerateGlobalSeed(); private uint v1, v2, v3, v4; private uint queue1, queue2, queue3; private uint length; /// /// Initializes the default seed. /// /// A random seed. private static unsafe uint GenerateGlobalSeed() { byte[] bytes = new byte[4]; using (RandomNumberGenerator generator = RandomNumberGenerator.Create()) { generator.GetBytes(bytes); } return BitConverter.ToUInt32(bytes, 0); } /// /// Adds a single value to the current hash. /// /// The type of the value to add into the hash code. /// The value to add into the hash code. public void Add(T value) { Add(value?.GetHashCode() ?? 0); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void Initialize(out uint v1, out uint v2, out uint v3, out uint v4) { v1 = seed + Prime1 + Prime2; v2 = seed + Prime2; v3 = seed; v4 = seed - Prime1; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Round(uint hash, uint input) { return RotateLeft(hash + input * Prime2, 13) * Prime1; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint QueueRound(uint hash, uint queuedValue) { return RotateLeft(hash + queuedValue * Prime3, 17) * Prime4; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint MixState(uint v1, uint v2, uint v3, uint v4) { return RotateLeft(v1, 1) + RotateLeft(v2, 7) + RotateLeft(v3, 12) + RotateLeft(v4, 18); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint MixEmptyState() { return seed + Prime5; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint MixFinal(uint hash) { hash ^= hash >> 15; hash *= Prime2; hash ^= hash >> 13; hash *= Prime3; hash ^= hash >> 16; return hash; } private void Add(int value) { uint val = (uint)value; uint previousLength = this.length++; uint position = previousLength % 4; if (position == 0) { this.queue1 = val; } else if (position == 1) { this.queue2 = val; } else if (position == 2) { this.queue3 = val; } else { if (previousLength == 3) { Initialize(out this.v1, out this.v2, out this.v3, out this.v4); } this.v1 = Round(this.v1, this.queue1); this.v2 = Round(this.v2, this.queue2); this.v3 = Round(this.v3, this.queue3); this.v4 = Round(this.v4, val); } } /// /// Gets the resulting hashcode from the current instance. /// /// The resulting hashcode from the current instance. public int ToHashCode() { uint length = this.length; uint position = length % 4; uint hash = length < 4 ? MixEmptyState() : MixState(this.v1, this.v2, this.v3, this.v4); hash += length * 4; if (position > 0) { hash = QueueRound(hash, this.queue1); if (position > 1) { hash = QueueRound(hash, this.queue2); if (position > 2) { hash = QueueRound(hash, this.queue3); } } } hash = MixFinal(hash); return (int)hash; } /// [Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes. Use ToHashCode to retrieve the computed hash code.", error: true)] [EditorBrowsable(EditorBrowsableState.Never)] public override int GetHashCode() => throw new NotSupportedException(); /// [Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes.", error: true)] [EditorBrowsable(EditorBrowsableState.Never)] public override bool Equals(object? obj) => throw new NotSupportedException(); /// /// Rotates the specified value left by the specified number of bits. /// Similar in behavior to the x86 instruction ROL. /// /// The value to rotate. /// The number of bits to rotate by. /// Any value outside the range [0..31] is treated as congruent mod 32. /// The rotated value. [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint RotateLeft(uint value, int offset) { return (value << offset) | (value >> (32 - offset)); } }