if (typeof(TKey) == typeof(string) &&
(ReferenceEquals(comparer, EqualityComparer<TKey>.Default) || ReferenceEquals(comparer, StringComparer.Ordinal) || ReferenceEquals(comparer, StringComparer.OrdinalIgnoreCase)))
{
- Dictionary<string, TValue> stringEntries = (Dictionary<string, TValue>)(object)source;
IEqualityComparer<string> stringComparer = (IEqualityComparer<string>)(object)comparer;
- // this array is needed for every strategy
- string[] entries = (string[])(object)source.Keys.ToArray();
+ // keys and values are needed for every strategy
+ string[] keys = (string[])(object)source.Keys.ToArray();
+ TValue[] values = source.Values.ToArray();
// Calculate the minimum and maximum lengths of the strings in the dictionary. Several of the analyses need this.
int minLength = int.MaxValue, maxLength = 0;
- foreach (string key in entries)
+ foreach (string key in keys)
{
if (key.Length < minLength) minLength = key.Length;
if (key.Length > maxLength) maxLength = key.Length;
Debug.Assert(minLength >= 0 && maxLength >= minLength);
// Try to create an implementation that uses length buckets, where each bucket contains up to only a few strings of the same length.
- FrozenDictionary<string, TValue>? frozenDictionary = LengthBucketsFrozenDictionary<TValue>.CreateLengthBucketsFrozenDictionaryIfAppropriate(stringEntries, stringComparer, minLength, maxLength, entries);
+ FrozenDictionary<string, TValue>? frozenDictionary = LengthBucketsFrozenDictionary<TValue>.CreateLengthBucketsFrozenDictionaryIfAppropriate(keys, values, stringComparer, minLength, maxLength);
if (frozenDictionary is not null)
{
return (FrozenDictionary<TKey, TValue>)(object)frozenDictionary;
}
// Analyze the keys for unique substrings and create an implementation that minimizes the cost of hashing keys.
- KeyAnalyzer.AnalysisResults analysis = KeyAnalyzer.Analyze(entries, ReferenceEquals(stringComparer, StringComparer.OrdinalIgnoreCase), minLength, maxLength);
+ KeyAnalyzer.AnalysisResults analysis = KeyAnalyzer.Analyze(keys, ReferenceEquals(stringComparer, StringComparer.OrdinalIgnoreCase), minLength, maxLength);
if (analysis.SubstringHashing)
{
if (analysis.RightJustifiedSubstring)
if (analysis.IgnoreCase)
{
frozenDictionary = analysis.AllAsciiIfIgnoreCase
- ? new OrdinalStringFrozenDictionary_RightJustifiedCaseInsensitiveAsciiSubstring<TValue>(stringEntries, entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount)
- : new OrdinalStringFrozenDictionary_RightJustifiedCaseInsensitiveSubstring<TValue>(stringEntries, entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount);
+ ? new OrdinalStringFrozenDictionary_RightJustifiedCaseInsensitiveAsciiSubstring<TValue>(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount)
+ : new OrdinalStringFrozenDictionary_RightJustifiedCaseInsensitiveSubstring<TValue>(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount);
}
else
{
frozenDictionary = analysis.HashCount == 1
- ? new OrdinalStringFrozenDictionary_RightJustifiedSingleChar<TValue>(stringEntries, entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex)
- : new OrdinalStringFrozenDictionary_RightJustifiedSubstring<TValue>(stringEntries, entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount);
+ ? new OrdinalStringFrozenDictionary_RightJustifiedSingleChar<TValue>(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex)
+ : new OrdinalStringFrozenDictionary_RightJustifiedSubstring<TValue>(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount);
}
}
else
if (analysis.IgnoreCase)
{
frozenDictionary = analysis.AllAsciiIfIgnoreCase
- ? new OrdinalStringFrozenDictionary_LeftJustifiedCaseInsensitiveAsciiSubstring<TValue>(stringEntries, entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount)
- : new OrdinalStringFrozenDictionary_LeftJustifiedCaseInsensitiveSubstring<TValue>(stringEntries, entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount);
+ ? new OrdinalStringFrozenDictionary_LeftJustifiedCaseInsensitiveAsciiSubstring<TValue>(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount)
+ : new OrdinalStringFrozenDictionary_LeftJustifiedCaseInsensitiveSubstring<TValue>(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount);
}
else
{
frozenDictionary = analysis.HashCount == 1
- ? new OrdinalStringFrozenDictionary_LeftJustifiedSingleChar<TValue>(stringEntries, entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex)
- : new OrdinalStringFrozenDictionary_LeftJustifiedSubstring<TValue>(stringEntries, entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount);
+ ? new OrdinalStringFrozenDictionary_LeftJustifiedSingleChar<TValue>(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex)
+ : new OrdinalStringFrozenDictionary_LeftJustifiedSubstring<TValue>(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff, analysis.HashIndex, analysis.HashCount);
}
}
}
if (analysis.IgnoreCase)
{
frozenDictionary = analysis.AllAsciiIfIgnoreCase
- ? new OrdinalStringFrozenDictionary_FullCaseInsensitiveAscii<TValue>(stringEntries, entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff)
- : new OrdinalStringFrozenDictionary_FullCaseInsensitive<TValue>(stringEntries, entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff);
+ ? new OrdinalStringFrozenDictionary_FullCaseInsensitiveAscii<TValue>(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff)
+ : new OrdinalStringFrozenDictionary_FullCaseInsensitive<TValue>(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff);
}
else
{
- frozenDictionary = new OrdinalStringFrozenDictionary_Full<TValue>(stringEntries, entries, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff);
+ frozenDictionary = new OrdinalStringFrozenDictionary_Full<TValue>(keys, values, stringComparer, analysis.MinimumLength, analysis.MaximumLengthDiff);
}
}
}
/// <summary>Initializes a frozen hash table.</summary>
- /// <param name="entriesLength">The number of entries to track from the hash table.</param>
- /// <param name="hashAtIndex">A delegate that produces a hash code for a given entry. It's passed the index of the entry and returns that entry's hash code.</param>
+ /// <param name="hashCodes">Pre-calculated hash codes.</param>
/// <param name="storeDestIndexFromSrcIndex">A delegate that assigns the index to a specific entry. It's passed the destination and source indices.</param>
/// <param name="optimizeForReading">true to spend additional effort tuning for subsequent read speed on the table; false to prioritize construction time.</param>
/// <remarks>
/// then uses this index to reference individual entries by indexing into <see cref="HashCodes"/>.
/// </remarks>
/// <returns>A frozen hash table.</returns>
- public static FrozenHashTable Create(int entriesLength, Func<int, int> hashAtIndex, Action<int, int> storeDestIndexFromSrcIndex, bool optimizeForReading = true)
+ public static FrozenHashTable Create(ReadOnlySpan<int> hashCodes, Action<int, int> storeDestIndexFromSrcIndex, bool optimizeForReading = true)
{
- Debug.Assert(entriesLength != 0);
-
- // Calculate the hashcodes for every entry.
- int[] arrayPoolHashCodes = ArrayPool<int>.Shared.Rent(entriesLength);
- Span<int> hashCodes = arrayPoolHashCodes.AsSpan(0, entriesLength);
- for (int i = 0; i < entriesLength; i++)
- {
- hashCodes[i] = hashAtIndex(i);
- }
-
// Determine how many buckets to use. This might be fewer than the number of entries
// if any entries have identical hashcodes (not just different hashcodes that might
// map to the same bucket).
Debug.Assert(count == hashtableHashcodes.Length);
ArrayPool<int>.Shared.Return(arrayPoolBuckets);
- ArrayPool<int>.Shared.Return(arrayPoolHashCodes);
return new FrozenHashTable(hashtableHashcodes, hashtableBuckets, fastModMultiplier);
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
_values = new TValue[entries.Length];
+ int[] arrayPoolHashCodes = ArrayPool<int>.Shared.Rent(entries.Length);
+ Span<int> hashCodes = arrayPoolHashCodes.AsSpan(0, entries.Length);
+ for (int i = 0; i < entries.Length; i++)
+ {
+ hashCodes[i] = entries[i].Key;
+ }
+
_hashTable = FrozenHashTable.Create(
- entries.Length,
- index => entries[index].Key,
+ hashCodes,
(destIndex, srcIndex) => _values[destIndex] = entries[srcIndex].Value);
+
+ ArrayPool<int>.Shared.Return(arrayPoolHashCodes);
}
/// <inheritdoc />
source.CopyTo(entries);
_hashTable = FrozenHashTable.Create(
- count,
- index => entries[index],
- delegate { });
+ new ReadOnlySpan<int>(entries, 0, count),
+ static delegate { });
ArrayPool<int>.Shared.Return(entries);
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
_items = new T[entries.Length];
+ int[] arrayPoolHashCodes = ArrayPool<int>.Shared.Rent(entries.Length);
+ Span<int> hashCodes = arrayPoolHashCodes.AsSpan(0, entries.Length);
+ for (int i = 0; i < entries.Length; i++)
+ {
+ hashCodes[i] = entries[i] is T t ? Comparer.GetHashCode(t) : 0;
+ }
+
_hashTable = FrozenHashTable.Create(
- entries.Length,
- index => entries[index] is T t ? Comparer.GetHashCode(t) : 0,
+ hashCodes,
(destIndex, srcIndex) => _items[destIndex] = entries[srcIndex],
optimizeForReading);
+
+ ArrayPool<int>.Shared.Return(arrayPoolHashCodes);
}
/// <inheritdoc />
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
_keys = new TKey[entries.Length];
_values = new TValue[entries.Length];
+ int[] arrayPoolHashCodes = ArrayPool<int>.Shared.Rent(entries.Length);
+ Span<int> hashCodes = arrayPoolHashCodes.AsSpan(0, entries.Length);
+ for (int i = 0; i < entries.Length; i++)
+ {
+ hashCodes[i] = Comparer.GetHashCode(entries[i].Key);
+ }
+
_hashTable = FrozenHashTable.Create(
- entries.Length,
- index => Comparer.GetHashCode(entries[index].Key),
+ hashCodes,
(destIndex, srcIndex) =>
{
_keys[destIndex] = entries[srcIndex].Key;
_values[destIndex] = entries[srcIndex].Value;
},
optimizeForReading);
+
+ ArrayPool<int>.Shared.Return(arrayPoolHashCodes);
}
/// <inheritdoc />
{
set.Clear();
- // SufficientUniquenessFactor of 95% is good enough.
+ // Sufficient uniqueness factor of 95% is good enough.
// Instead of ensuring that 95% of data is good, we stop when we know that at least 5% is bad.
int acceptableNonUniqueCount = uniqueStrings.Length / 20;
}
internal static LengthBucketsFrozenDictionary<TValue>? CreateLengthBucketsFrozenDictionaryIfAppropriate(
- Dictionary<string, TValue> source, IEqualityComparer<string> comparer, int minLength, int maxLength, string[] keys)
+ string[] keys, TValue[] values, IEqualityComparer<string> comparer, int minLength, int maxLength)
{
- Debug.Assert(source.Count != 0);
+ Debug.Assert(keys.Length != 0 && keys.Length == values.Length);
Debug.Assert(comparer == EqualityComparer<string>.Default || comparer == StringComparer.Ordinal || comparer == StringComparer.OrdinalIgnoreCase);
Debug.Assert(minLength >= 0 && maxLength >= minLength);
// If without even looking at the keys we know that some bucket will exceed the max per-bucket
// limit (pigeon hole principle), we can early-exit out without doing any further work.
int spread = maxLength - minLength + 1;
- if (source.Count / spread > MaxPerLength)
+ if (keys.Length / spread > MaxPerLength)
{
return null;
}
// Iterate through all of the inputs, bucketing them based on the length of the string.
var groupedByLength = new Dictionary<int, List<KeyValuePair<string, TValue>>>();
- foreach (KeyValuePair<string, TValue> pair in source)
+ for (int i = 0; i < keys.Length; i++)
{
- string s = pair.Key;
+ string s = keys[i];
Debug.Assert(s.Length >= minLength && s.Length <= maxLength);
#if NET6_0_OR_GREATER
return null;
}
- list.Add(pair);
+ list.Add(new KeyValuePair<string, TValue>(s, values[i]));
}
// If there would be too much empty space in the lookup array, bail.
return null;
}
- var values = new TValue[keys.Length];
var lengthBuckets = new KeyValuePair<string, int>[spread][];
// Iterate through each bucket, filling the keys/values arrays, and creating a lookup array such that
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
private readonly int _maximumLengthDiff;
internal OrdinalStringFrozenDictionary(
- Dictionary<string, TValue> source,
string[] keys,
+ TValue[] values,
IEqualityComparer<string> comparer,
int minimumLength,
int maximumLengthDiff,
int hashCount = -1) :
base(comparer)
{
- Debug.Assert(source.Count != 0);
+ Debug.Assert(keys.Length != 0 && keys.Length == values.Length);
Debug.Assert(comparer == EqualityComparer<string>.Default || comparer == StringComparer.Ordinal || comparer == StringComparer.OrdinalIgnoreCase);
- var entries = new KeyValuePair<string, TValue>[source.Count];
- ((ICollection<KeyValuePair<string, TValue>>)source).CopyTo(entries, 0);
+ // we need an extra copy, as the order of items will change
+ _keys = new string[keys.Length];
+ _values = new TValue[values.Length];
- _keys = keys;
- _values = new TValue[entries.Length];
_minimumLength = minimumLength;
_maximumLengthDiff = maximumLengthDiff;
HashIndex = hashIndex;
HashCount = hashCount;
+ int[] arrayPoolHashCodes = ArrayPool<int>.Shared.Rent(keys.Length);
+ Span<int> hashCodes = arrayPoolHashCodes.AsSpan(0, keys.Length);
+ for (int i = 0; i < keys.Length; i++)
+ {
+ hashCodes[i] = GetHashCode(keys[i]);
+ }
+
_hashTable = FrozenHashTable.Create(
- entries.Length,
- index => GetHashCode(entries[index].Key),
+ hashCodes,
(destIndex, srcIndex) =>
{
- _keys[destIndex] = entries[srcIndex].Key;
- _values[destIndex] = entries[srcIndex].Value;
+ _keys[destIndex] = keys[srcIndex];
+ _values[destIndex] = values[srcIndex];
});
+
+ ArrayPool<int>.Shared.Return(arrayPoolHashCodes);
}
private protected int HashIndex { get; }
internal sealed class OrdinalStringFrozenDictionary_Full<TValue> : OrdinalStringFrozenDictionary<TValue>
{
internal OrdinalStringFrozenDictionary_Full(
- Dictionary<string, TValue> source,
string[] keys,
+ TValue[] values,
IEqualityComparer<string> comparer,
int minimumLength,
int maximumLengthDiff)
- : base(source, keys, comparer, minimumLength, maximumLengthDiff)
+ : base(keys, values, comparer, minimumLength, maximumLengthDiff)
{
}
internal sealed class OrdinalStringFrozenDictionary_FullCaseInsensitive<TValue> : OrdinalStringFrozenDictionary<TValue>
{
internal OrdinalStringFrozenDictionary_FullCaseInsensitive(
- Dictionary<string, TValue> source,
string[] keys,
+ TValue[] values,
IEqualityComparer<string> comparer,
int minimumLength,
int maximumLengthDiff)
- : base(source, keys, comparer, minimumLength, maximumLengthDiff)
+ : base(keys, values, comparer, minimumLength, maximumLengthDiff)
{
}
internal sealed class OrdinalStringFrozenDictionary_FullCaseInsensitiveAscii<TValue> : OrdinalStringFrozenDictionary<TValue>
{
internal OrdinalStringFrozenDictionary_FullCaseInsensitiveAscii(
- Dictionary<string, TValue> source,
string[] keys,
+ TValue[] values,
IEqualityComparer<string> comparer,
int minimumLength,
int maximumLengthDiff)
- : base(source, keys, comparer, minimumLength, maximumLengthDiff)
+ : base(keys, values, comparer, minimumLength, maximumLengthDiff)
{
}
internal sealed class OrdinalStringFrozenDictionary_LeftJustifiedCaseInsensitiveAsciiSubstring<TValue> : OrdinalStringFrozenDictionary<TValue>
{
internal OrdinalStringFrozenDictionary_LeftJustifiedCaseInsensitiveAsciiSubstring(
- Dictionary<string, TValue> source,
string[] keys,
+ TValue[] values,
IEqualityComparer<string> comparer,
int minimumLength,
int maximumLengthDiff,
int hashIndex,
int hashCount)
- : base(source, keys, comparer, minimumLength, maximumLengthDiff, hashIndex, hashCount)
+ : base(keys, values, comparer, minimumLength, maximumLengthDiff, hashIndex, hashCount)
{
}
internal sealed class OrdinalStringFrozenDictionary_LeftJustifiedCaseInsensitiveSubstring<TValue> : OrdinalStringFrozenDictionary<TValue>
{
internal OrdinalStringFrozenDictionary_LeftJustifiedCaseInsensitiveSubstring(
- Dictionary<string, TValue> source,
string[] keys,
+ TValue[] values,
IEqualityComparer<string> comparer,
int minimumLength,
int maximumLengthDiff,
int hashIndex,
int hashCount)
- : base(source, keys, comparer, minimumLength, maximumLengthDiff, hashIndex, hashCount)
+ : base(keys, values, comparer, minimumLength, maximumLengthDiff, hashIndex, hashCount)
{
}
internal sealed class OrdinalStringFrozenDictionary_LeftJustifiedSingleChar<TValue> : OrdinalStringFrozenDictionary<TValue>
{
internal OrdinalStringFrozenDictionary_LeftJustifiedSingleChar(
- Dictionary<string, TValue> source,
string[] keys,
+ TValue[] values,
IEqualityComparer<string> comparer,
int minimumLength,
int maximumLengthDiff,
int hashIndex)
- : base(source, keys, comparer, minimumLength, maximumLengthDiff, hashIndex, 1)
+ : base(keys, values, comparer, minimumLength, maximumLengthDiff, hashIndex, 1)
{
}
internal sealed class OrdinalStringFrozenDictionary_LeftJustifiedSubstring<TValue> : OrdinalStringFrozenDictionary<TValue>
{
internal OrdinalStringFrozenDictionary_LeftJustifiedSubstring(
- Dictionary<string, TValue> source,
string[] keys,
+ TValue[] values,
IEqualityComparer<string> comparer,
int minimumLength,
int maximumLengthDiff,
int hashIndex,
int hashCount)
- : base(source, keys, comparer, minimumLength, maximumLengthDiff, hashIndex, hashCount)
+ : base(keys, values, comparer, minimumLength, maximumLengthDiff, hashIndex, hashCount)
{
}
internal sealed class OrdinalStringFrozenDictionary_RightJustifiedCaseInsensitiveAsciiSubstring<TValue> : OrdinalStringFrozenDictionary<TValue>
{
internal OrdinalStringFrozenDictionary_RightJustifiedCaseInsensitiveAsciiSubstring(
- Dictionary<string, TValue> source,
string[] keys,
+ TValue[] values,
IEqualityComparer<string> comparer,
int minimumLength,
int maximumLengthDiff,
int hashIndex,
int hashCount)
- : base(source, keys, comparer, minimumLength, maximumLengthDiff, hashIndex, hashCount)
+ : base(keys, values, comparer, minimumLength, maximumLengthDiff, hashIndex, hashCount)
{
}
internal sealed class OrdinalStringFrozenDictionary_RightJustifiedCaseInsensitiveSubstring<TValue> : OrdinalStringFrozenDictionary<TValue>
{
internal OrdinalStringFrozenDictionary_RightJustifiedCaseInsensitiveSubstring(
- Dictionary<string, TValue> source,
string[] keys,
+ TValue[] values,
IEqualityComparer<string> comparer,
int minimumLength,
int maximumLengthDiff,
int hashIndex,
int hashCount)
- : base(source, keys, comparer, minimumLength, maximumLengthDiff, hashIndex, hashCount)
+ : base(keys, values, comparer, minimumLength, maximumLengthDiff, hashIndex, hashCount)
{
}
internal sealed class OrdinalStringFrozenDictionary_RightJustifiedSingleChar<TValue> : OrdinalStringFrozenDictionary<TValue>
{
internal OrdinalStringFrozenDictionary_RightJustifiedSingleChar(
- Dictionary<string, TValue> source,
string[] keys,
+ TValue[] values,
IEqualityComparer<string> comparer,
int minimumLength,
int maximumLengthDiff,
int hashIndex)
- : base(source, keys, comparer, minimumLength, maximumLengthDiff, hashIndex, 1)
+ : base(keys, values, comparer, minimumLength, maximumLengthDiff, hashIndex, 1)
{
}
internal sealed class OrdinalStringFrozenDictionary_RightJustifiedSubstring<TValue> : OrdinalStringFrozenDictionary<TValue>
{
internal OrdinalStringFrozenDictionary_RightJustifiedSubstring(
- Dictionary<string, TValue> source,
string[] keys,
+ TValue[] values,
IEqualityComparer<string> comparer,
int minimumLength,
int maximumLengthDiff,
int hashIndex,
int hashCount)
- : base(source, keys, comparer, minimumLength, maximumLengthDiff, hashIndex, hashCount)
+ : base(keys, values, comparer, minimumLength, maximumLengthDiff, hashIndex, hashCount)
{
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Buffers;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
HashIndex = hashIndex;
HashCount = hashCount;
+ int[] arrayPoolHashCodes = ArrayPool<int>.Shared.Rent(entries.Length);
+ Span<int> hashCodes = arrayPoolHashCodes.AsSpan(0, entries.Length);
+ for (int i = 0; i < entries.Length; i++)
+ {
+ hashCodes[i] = GetHashCode(entries[i]);
+ }
+
_hashTable = FrozenHashTable.Create(
- entries.Length,
- index => GetHashCode(entries[index]),
+ hashCodes,
(destIndex, srcIndex) => _items[destIndex] = entries[srcIndex]);
+
+ ArrayPool<int>.Shared.Return(arrayPoolHashCodes);
}
private protected int HashIndex { get; }