From d269010181d99918832f991537dfd26f0d33b0b0 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 20 May 2024 16:59:52 -0700 Subject: [PATCH] Add FrozenSet.Create collection builder (#102223) --- .../ref/System.Collections.Immutable.cs | 3 ++ .../System/Collections/Frozen/FrozenSet.cs | 36 +++++++++++++++++++ .../tests/Frozen/FrozenSetTests.cs | 16 +++++++-- 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs b/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs index 9c086594cbc30..bf180cfb33dbc 100644 --- a/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs +++ b/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs @@ -70,8 +70,11 @@ void System.IDisposable.Dispose() { } } public static partial class FrozenSet { + public static System.Collections.Frozen.FrozenSet Create(params System.ReadOnlySpan source) { throw null; } + public static System.Collections.Frozen.FrozenSet Create(System.Collections.Generic.IEqualityComparer? equalityComparer, params System.ReadOnlySpan source) { throw null; } public static System.Collections.Frozen.FrozenSet ToFrozenSet(this System.Collections.Generic.IEnumerable source, System.Collections.Generic.IEqualityComparer? comparer = null) { throw null; } } + [System.Runtime.CompilerServices.CollectionBuilder(typeof(System.Collections.Frozen.FrozenSet), nameof(System.Collections.Frozen.FrozenSet.Create))] public abstract partial class FrozenSet : System.Collections.Generic.ICollection, System.Collections.Generic.IEnumerable, System.Collections.Generic.IReadOnlyCollection, System.Collections.Generic.ISet, System.Collections.ICollection, System.Collections.IEnumerable { internal FrozenSet() { } diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenSet.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenSet.cs index 79acea67bd100..2c48fc8d76e4c 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenSet.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenSet.cs @@ -5,6 +5,7 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace System.Collections.Frozen @@ -14,6 +15,40 @@ namespace System.Collections.Frozen /// public static class FrozenSet { + /// Creates a with the specified values. + /// The values to use to populate the set. + /// The type of the values in the set. + /// A frozen set. + public static FrozenSet Create(params ReadOnlySpan source) => Create(null, source); + + /// Creates a with the specified values. + /// The values to use to populate the set. + /// The comparer implementation to use to compare values for equality. If null, is used. + /// The type of the values in the set. + /// A frozen set. + public static FrozenSet Create(IEqualityComparer? equalityComparer, params ReadOnlySpan source) + { + if (source.Length == 0) + { + return equalityComparer is null || ReferenceEquals(equalityComparer, FrozenSet.Empty.Comparer) ? + FrozenSet.Empty : + new EmptyFrozenSet(equalityComparer); + } + + HashSet set = +#if NET + new(source.Length, equalityComparer); // we assume there are few-to-no duplicates when using this API +#else + new(equalityComparer); +#endif + foreach (T item in source) + { + set.Add(item); + } + + return ToFrozenSet(set, equalityComparer); + } + /// Creates a with the specified values. /// The values to use to populate the set. /// The comparer implementation to use to compare values for equality. If null, is used. @@ -199,6 +234,7 @@ private static FrozenSet CreateFromSet(HashSet source) /// the remainder of the life of the application. should only be initialized /// with trusted elements, as the details of the elements impacts construction time. /// + [CollectionBuilder(typeof(FrozenSet), nameof(FrozenSet.Create))] [DebuggerTypeProxy(typeof(ImmutableEnumerableDebuggerProxy<>))] [DebuggerDisplay("Count = {Count}")] public abstract class FrozenSet : ISet, diff --git a/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenSetTests.cs b/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenSetTests.cs index 717efc31e45bb..989292fe7b4f2 100644 --- a/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenSetTests.cs +++ b/src/libraries/System.Collections.Immutable/tests/Frozen/FrozenSetTests.cs @@ -80,6 +80,16 @@ public void EmptySource_ProducedFrozenSetEmpty() Assert.NotSame(FrozenSet.Empty, source.ToFrozenSet(NonDefaultEqualityComparer.Instance)); } + + Assert.Same(FrozenSet.Empty, FrozenSet.Create(ReadOnlySpan.Empty)); + Assert.Same(FrozenSet.Empty, FrozenSet.Create()); + + foreach (IEqualityComparer comparer in new IEqualityComparer[] { null, EqualityComparer.Default }) + { + Assert.Same(FrozenSet.Empty, FrozenSet.Create(comparer)); + } + + Assert.NotSame(FrozenSet.Empty, FrozenSet.Create(NonDefaultEqualityComparer.Instance)); } [Fact] @@ -387,7 +397,7 @@ public class FrozenSet_Generic_Tests_string_OrdinalIgnoreCase : FrozenSet_Generi [Fact] public void TryGetValue_FindsExpectedResult() { - FrozenSet frozen = new[] { "abc" }.ToFrozenSet(StringComparer.OrdinalIgnoreCase); + FrozenSet frozen = FrozenSet.Create(StringComparer.OrdinalIgnoreCase, "abc"); Assert.False(frozen.TryGetValue("ab", out string actualValue)); Assert.Null(actualValue); @@ -498,7 +508,7 @@ public void Sparse_LookupItems_AlltemsFoundAsExpected() foreach (int skip in new[] { 2, 3, 5 }) { var original = new HashSet(Enumerable.Range(-3, size).Where(i => i % skip == 0)); - FrozenSet frozen = original.ToFrozenSet(); + FrozenSet frozen = [.. original]; for (int i = -10; i <= size + 66; i++) { @@ -539,7 +549,7 @@ public void ClosedRange_Lookup_AllItemsFoundAsExpected() original.Add(i); } - FrozenSet frozen = original.ToFrozenSet(); + FrozenSet frozen = [.. original]; min = start > long.MinValue ? start - 10 : start; max = start + size - 1 < long.MaxValue ? start + size + 9 : start + size - 1;