Skip to content

Commit c08ef4b

Browse files
authored
Add FrozenDictionary.Create (#114192)
1 parent 5854863 commit c08ef4b

File tree

4 files changed

+107
-10
lines changed

4 files changed

+107
-10
lines changed

src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ namespace System.Collections.Frozen
88
{
99
public static partial class FrozenDictionary
1010
{
11+
public static System.Collections.Frozen.FrozenDictionary<TKey, TValue> Create<TKey, TValue>(params System.ReadOnlySpan<System.Collections.Generic.KeyValuePair<TKey, TValue>> source) where TKey : notnull { throw null; }
12+
public static System.Collections.Frozen.FrozenDictionary<TKey, TValue> Create<TKey, TValue>(System.Collections.Generic.IEqualityComparer<TKey>? comparer, params System.ReadOnlySpan<System.Collections.Generic.KeyValuePair<TKey, TValue>> source) where TKey : notnull { throw null; }
1113
public static System.Collections.Frozen.FrozenDictionary<TKey, TValue> ToFrozenDictionary<TKey, TValue>(this System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<TKey, TValue>> source, System.Collections.Generic.IEqualityComparer<TKey>? comparer = null) where TKey : notnull { throw null; }
1214
public static System.Collections.Frozen.FrozenDictionary<TKey, TSource> ToFrozenDictionary<TSource, TKey>(this System.Collections.Generic.IEnumerable<TSource> source, System.Func<TSource, TKey> keySelector, System.Collections.Generic.IEqualityComparer<TKey>? comparer = null) where TKey : notnull { throw null; }
1315
public static System.Collections.Frozen.FrozenDictionary<TKey, TElement> ToFrozenDictionary<TSource, TKey, TElement>(this System.Collections.Generic.IEnumerable<TSource> source, System.Func<TSource, TKey> keySelector, System.Func<TSource, TElement> elementSelector, System.Collections.Generic.IEqualityComparer<TKey>? comparer = null) where TKey : notnull { throw null; }
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!-- https://learn.microsoft.com/dotnet/fundamentals/package-validation/diagnostic-ids -->
3+
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
4+
<Suppression>
5+
<DiagnosticId>CP0016</DiagnosticId>
6+
<Target>T:System.Collections.Frozen.FrozenDictionary`2:[T:System.Runtime.CompilerServices.CollectionBuilderAttribute]</Target>
7+
<Left>ref/net10.0/System.Collections.Immutable.dll</Left>
8+
<Right>lib/net10.0/System.Collections.Immutable.dll</Right>
9+
</Suppression>
10+
<Suppression>
11+
<DiagnosticId>CP0016</DiagnosticId>
12+
<Target>T:System.Collections.Frozen.FrozenDictionary`2:[T:System.Runtime.CompilerServices.CollectionBuilderAttribute]</Target>
13+
<Left>ref/net8.0/System.Collections.Immutable.dll</Left>
14+
<Right>lib/net8.0/System.Collections.Immutable.dll</Right>
15+
</Suppression>
16+
<Suppression>
17+
<DiagnosticId>CP0016</DiagnosticId>
18+
<Target>T:System.Collections.Frozen.FrozenDictionary`2:[T:System.Runtime.CompilerServices.CollectionBuilderAttribute]</Target>
19+
<Left>ref/net9.0/System.Collections.Immutable.dll</Left>
20+
<Right>lib/net9.0/System.Collections.Immutable.dll</Right>
21+
</Suppression>
22+
</Suppressions>

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

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,50 @@ namespace System.Collections.Frozen
1616
/// </summary>
1717
public static class FrozenDictionary
1818
{
19+
/// <summary>Creates a <see cref="FrozenDictionary{TKey, TValue}"/> with the specified key/value pairs.</summary>
20+
/// <param name="source">The key/value pairs to use to populate the dictionary.</param>
21+
/// <typeparam name="TKey">The type of the keys in the dictionary.</typeparam>
22+
/// <typeparam name="TValue">The type of the values in the dictionary.</typeparam>
23+
/// <remarks>
24+
/// If the same key appears multiple times in the input, the latter one in the sequence takes precedence. This differs from
25+
/// <see cref="M:System.Linq.Enumerable.ToDictionary"/>, with which multiple duplicate keys will result in an exception.
26+
/// </remarks>
27+
/// <returns>A <see cref="FrozenDictionary{TKey, TValue}"/> that contains the specified keys and values.</returns>
28+
public static FrozenDictionary<TKey, TValue> Create<TKey, TValue>(params ReadOnlySpan<KeyValuePair<TKey, TValue>> source)
29+
where TKey : notnull =>
30+
Create(null, source);
31+
32+
/// <summary>Creates a <see cref="FrozenDictionary{TKey, TValue}"/> with the specified key/value pairs.</summary>
33+
/// <param name="source">The key/value pairs to use to populate the dictionary.</param>
34+
/// <param name="comparer">The comparer implementation to use to compare keys for equality. If <see langword="null"/>, <see cref="EqualityComparer{TKey}.Default"/> is used.</param>
35+
/// <typeparam name="TKey">The type of the keys in the dictionary.</typeparam>
36+
/// <typeparam name="TValue">The type of the values in the dictionary.</typeparam>
37+
/// <remarks>
38+
/// If the same key appears multiple times in the input, the latter one in the sequence takes precedence. This differs from
39+
/// <see cref="M:System.Linq.Enumerable.ToDictionary"/>, with which multiple duplicate keys will result in an exception.
40+
/// </remarks>
41+
/// <returns>A <see cref="FrozenDictionary{TKey, TValue}"/> that contains the specified keys and values.</returns>
42+
public static FrozenDictionary<TKey, TValue> Create<TKey, TValue>(IEqualityComparer<TKey>? comparer, params ReadOnlySpan<KeyValuePair<TKey, TValue>> source)
43+
where TKey : notnull
44+
{
45+
comparer ??= EqualityComparer<TKey>.Default;
46+
47+
if (source.IsEmpty)
48+
{
49+
return ReferenceEquals(comparer, FrozenDictionary<TKey, TValue>.Empty.Comparer) ?
50+
FrozenDictionary<TKey, TValue>.Empty :
51+
new EmptyFrozenDictionary<TKey, TValue>(comparer);
52+
}
53+
54+
Dictionary<TKey, TValue> d = new(source.Length, comparer);
55+
foreach (KeyValuePair<TKey, TValue> pair in source)
56+
{
57+
d[pair.Key] = pair.Value;
58+
}
59+
60+
return CreateFromDictionary(d);
61+
}
62+
1963
/// <summary>Creates a <see cref="FrozenDictionary{TKey, TValue}"/> with the specified key/value pairs.</summary>
2064
/// <param name="source">The key/value pairs to use to populate the dictionary.</param>
2165
/// <param name="comparer">The comparer implementation to use to compare keys for equality. If null, <see cref="EqualityComparer{TKey}.Default"/> is used.</param>
@@ -259,6 +303,7 @@ private static FrozenDictionary<TKey, TValue> CreateFromDictionary<TKey, TValue>
259303
/// the remainder of the life of the application. <see cref="FrozenDictionary{TKey, TValue}"/> should only be
260304
/// initialized with trusted keys, as the details of the keys impacts construction time.
261305
/// </remarks>
306+
[CollectionBuilder(typeof(FrozenDictionary), nameof(FrozenDictionary.Create))]
262307
[DebuggerTypeProxy(typeof(ImmutableDictionaryDebuggerProxy<,>))]
263308
[DebuggerDisplay("Count = {Count}")]
264309
public abstract partial class FrozenDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue>, IDictionary

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

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public void NullSource_ThrowsException()
8585
}
8686

8787
[Fact]
88-
public void EmptySource_ProducedFrozenDictionaryEmpty()
88+
public void EmptyEnumerableSource_ProducedFrozenDictionaryEmpty()
8989
{
9090
IEnumerable<KeyValuePair<TKey, TValue>>[] sources = new[]
9191
{
@@ -118,6 +118,23 @@ public void EmptySource_ProducedFrozenDictionaryEmpty()
118118
}
119119
}
120120

121+
[Fact]
122+
public void EmptySpanSource_ProducedFrozenDictionaryEmpty()
123+
{
124+
ReadOnlySpan<KeyValuePair<TKey, TValue>> source = default;
125+
126+
Assert.Same(FrozenDictionary<TKey, TValue>.Empty, FrozenDictionary.Create(source));
127+
128+
foreach (IEqualityComparer<TKey> comparer in new IEqualityComparer<TKey>[] { null, EqualityComparer<TKey>.Default })
129+
{
130+
Assert.Same(FrozenDictionary<TKey, TValue>.Empty, FrozenDictionary.Create(comparer, source));
131+
}
132+
133+
Assert.NotSame(FrozenDictionary<TKey, TValue>.Empty, FrozenDictionary.Create(NonDefaultEqualityComparer<TKey>.Instance, source));
134+
135+
Assert.Equal(0, FrozenDictionary.Create(NonDefaultEqualityComparer<TKey>.Instance, source).Count);
136+
}
137+
121138
[Fact]
122139
public void EmptyFrozenDictionary_Idempotent()
123140
{
@@ -202,23 +219,28 @@ public void ToFrozenDictionary_KeySelectorAndValueSelector_ResultsAreUsed()
202219
}
203220

204221
public static IEnumerable<object[]> LookupItems_AllItemsFoundAsExpected_MemberData() =>
222+
from useToFrozenDictionary in new[] { false, true }
205223
from size in new[] { 0, 1, 2, 10, 99 }
206224
from comparer in new IEqualityComparer<TKey>[] { null, EqualityComparer<TKey>.Default, NonDefaultEqualityComparer<TKey>.Instance }
207225
from specifySameComparer in new[] { false, true }
208-
select new object[] { size, comparer, specifySameComparer };
226+
select new object[] { useToFrozenDictionary, size, comparer, specifySameComparer };
209227

210228
[Theory]
211229
[MemberData(nameof(LookupItems_AllItemsFoundAsExpected_MemberData))]
212-
public void LookupItems_AllItemsFoundAsExpected(int size, IEqualityComparer<TKey> comparer, bool specifySameComparer)
230+
public void LookupItems_AllItemsFoundAsExpected(bool useToFrozenDictionary, int size, IEqualityComparer<TKey> comparer, bool specifySameComparer)
213231
{
214232
Dictionary<TKey, TValue> original =
215233
GenerateUniqueKeyValuePairs(size)
216234
.ToDictionary(p => p.Key, p => p.Value, comparer);
217235
KeyValuePair<TKey, TValue>[] originalPairs = original.ToArray();
218236

219-
FrozenDictionary<TKey, TValue> frozen = specifySameComparer ?
220-
original.ToFrozenDictionary(comparer) :
221-
original.ToFrozenDictionary();
237+
FrozenDictionary<TKey, TValue> frozen = (useToFrozenDictionary, specifySameComparer) switch
238+
{
239+
(true, true) => original.ToFrozenDictionary(comparer),
240+
(true, false) => original.ToFrozenDictionary(),
241+
(false, true) => FrozenDictionary.Create(comparer, originalPairs),
242+
(false, false) => FrozenDictionary.Create(originalPairs),
243+
};
222244

223245
// Make sure creating the frozen dictionary didn't alter the original
224246
Assert.Equal(originalPairs.Length, original.Count);
@@ -283,8 +305,10 @@ public void EqualButPossiblyDifferentKeys_Found(bool fromDictionary)
283305
}
284306
}
285307

286-
[Fact]
287-
public void MultipleValuesSameKey_LastInSourceWins()
308+
[Theory]
309+
[InlineData(false)]
310+
[InlineData(true)]
311+
public void MultipleValuesSameKey_LastInSourceWins(bool useToFrozenDictionary)
288312
{
289313
TKey[] keys = GenerateUniqueKeyValuePairs(2).Select(pair => pair.Key).ToArray();
290314
TValue[] values = Enumerable.Range(0, 10).Select(CreateTValue).ToArray();
@@ -301,7 +325,9 @@ from value in values
301325
source = source.Reverse();
302326
}
303327

304-
FrozenDictionary<TKey, TValue> frozen = source.ToFrozenDictionary(GetKeyIEqualityComparer());
328+
FrozenDictionary<TKey, TValue> frozen = useToFrozenDictionary ?
329+
source.ToFrozenDictionary(GetKeyIEqualityComparer()) :
330+
FrozenDictionary.Create(GetKeyIEqualityComparer(), source.ToArray());
305331

306332
Assert.Equal(values[reverse ? 0 : values.Length - 1], frozen[keys[0]]);
307333
Assert.Equal(values[reverse ? 0 : values.Length - 1], frozen[keys[1]]);
@@ -387,7 +413,9 @@ public void ContainsKey_WithNonAscii(int percentageWithNonAscii)
387413
expected.Add(value, value);
388414
}
389415

390-
FrozenDictionary<string, string> actual = expected.ToFrozenDictionary(GetKeyIEqualityComparer());
416+
FrozenDictionary<string, string> actual = percentageWithNonAscii % 2 == 0 ?
417+
expected.ToFrozenDictionary(GetKeyIEqualityComparer()) :
418+
FrozenDictionary.Create(GetKeyIEqualityComparer(), expected.ToArray());
391419

392420
Assert.All(expected, kvp => actual.ContainsKey(kvp.Key));
393421
}

0 commit comments

Comments
 (0)