Skip to content

Commit 1f15afe

Browse files
committed
Pre-size intermediate Dictionary in ToFrozenDictionary
When the source is not already a Dictionary, ToFrozenDictionary builds an intermediate Dictionary to deduplicate keys with last-wins semantics. The previous code used the default-capacity constructor, which paid log2(N) resizes and ~2N redundant rehashes when populating from a known size source such as an array or List. Use ICollection<KeyValuePair>.Count as a capacity hint when available, mirroring the HashSet(IEnumerable, IEqualityComparer) constructor. The ReadOnlySpan overload of FrozenDictionary.Create already pre-sizes, so this brings the IEnumerable path in line with it. Adds MultipleValuesSameKey_ICollectionSource_LastInWins regression test to guard last-wins semantics over the pre-sized path.
1 parent f288fd5 commit 1f15afe

2 files changed

Lines changed: 27 additions & 1 deletion

File tree

src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenDictionary.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,8 @@ public static FrozenDictionary<TKey, TElement> ToFrozenDictionary<TSource, TKey,
131131
newDictionary = source as Dictionary<TKey, TValue>;
132132
if (newDictionary is null || (newDictionary.Count != 0 && !newDictionary.Comparer.Equals(comparer)))
133133
{
134-
newDictionary = new Dictionary<TKey, TValue>(comparer);
134+
int capacity = source is ICollection<KeyValuePair<TKey, TValue>> collection ? collection.Count : 0;
135+
newDictionary = new Dictionary<TKey, TValue>(capacity, comparer);
135136
foreach (KeyValuePair<TKey, TValue> pair in source)
136137
{
137138
// Dictionary's constructor uses Add, which will throw on duplicates.

src/libraries/System.Collections.Immutable/tests/Frozen/FrozenDictionaryTests.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,31 @@ from value in values
334334
}
335335
}
336336

337+
[Fact]
338+
public void MultipleValuesSameKey_ICollectionSource_LastInWins()
339+
{
340+
TKey[] keys = GenerateUniqueKeyValuePairs(2).Select(pair => pair.Key).ToArray();
341+
TValue[] values = Enumerable.Range(0, 10).Select(CreateTValue).ToArray();
342+
343+
KeyValuePair<TKey, TValue>[] pairs =
344+
(from key in keys
345+
from value in values
346+
select new KeyValuePair<TKey, TValue>(key, value)).ToArray();
347+
348+
foreach (IEnumerable<KeyValuePair<TKey, TValue>> source in new IEnumerable<KeyValuePair<TKey, TValue>>[]
349+
{
350+
pairs,
351+
new List<KeyValuePair<TKey, TValue>>(pairs),
352+
})
353+
{
354+
FrozenDictionary<TKey, TValue> frozen = source.ToFrozenDictionary(GetKeyIEqualityComparer());
355+
356+
Assert.Equal(values.Length, pairs.Length / keys.Length);
357+
Assert.Equal(values[values.Length - 1], frozen[keys[0]]);
358+
Assert.Equal(values[values.Length - 1], frozen[keys[1]]);
359+
}
360+
}
361+
337362
[Theory]
338363
[InlineData(0)]
339364
[InlineData(1)]

0 commit comments

Comments
 (0)