Skip to content

Commit

Permalink
Allow collection expressions for 'System.Collections.ObjectModel' col…
Browse files Browse the repository at this point in the history
…lection types (#111093)

* #110161 Allow collection expressions for 'System.Collections.ObjectModel' collection types
#110161

* Code review

Enhance ReadOnlyCollection with documentation and updates

The `EditorBrowsable` attribute is replaced with a `SuppressMessage` attribute to clarify the intended usage of these methods.

Additionally, the condition for checking if input values are empty is updated from `values.Length <= 0` to `values.IsEmpty` for improved readability and performance.

* Remove unused namespace from ReadOnlyCollection.cs

Eliminated the `using System.ComponentModel;` directive, reducing dependencies and potentially simplifying the code structure.

* Remove suppression attributes from collection methods

The `CreateCollection<T>` method in the `ReadOnlyCollection` class had its suppression attribute removed, addressing a style warning related to collection initialization while maintaining its functionality.

Similarly, the `CreateSet<T>` method in the `ReadOnlySet` class also had its suppression attribute removed, preserving its original functionality.

* Refactor `(HashSet<T>)[.. values]` to manual loop

* Marked CreateCollection and CreateSet methods in ReadOnlyCollection with the `[EditorBrowsable(EditorBrowsableState.Never)]` attribute to hide them from IntelliSense.

* Apply suggestions from code review

* Refactor ReadOnlyCollection and ReadOnlySet APIs

Updated method names in ReadOnlyCollection and ReadOnlySet from CreateCollection and CreateSet to Create for clarity and consistency.

Removed the CreateSet method from ReadOnlyCollection to streamline functionality.
Adjusted documentation comments for improved clarity.

Updated CollectionBuilder attributes to reflect the new method names, ensuring proper linkage for collection creation.

* Updated the `CollectionBuilder` attribute to reference `ReadOnlySet` instead of `ReadOnlyCollection`, reflecting a change in the collection type used in the `ReadOnlySet` class.

* Code Style

* Rename methods in ReadOnlyCollection and ReadOnlySet

Updated `ReadOnlyCollection` to rename `Create` to `CreateCollection` and introduced `CreateSet` for creating `ReadOnlySet` instances from spans. Adjusted `CollectionBuilder` attributes accordingly and removed the static `ReadOnlySet` class, integrating its functionality into the main class. Updated the `ReadOnlyCollection` partial class in `System.Runtime.cs` to maintain API consistency.

---------

Co-authored-by: Eirik Tsarpalis <[email protected]>
  • Loading branch information
AlexRadch and eiriktsarpalis authored Jan 10, 2025
1 parent 71db8b2 commit 22e39f9
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Linq;
using Xunit;

namespace System.Collections.ObjectModel.Tests
Expand All @@ -21,6 +22,24 @@ public void Ctor_SetProperty_Roundtrips()
Assert.Same(set, new DerivedReadOnlySet<int>(set).Set);
}

[Fact]
public static void Ctor_CollectionExpressions_Empty()
{
ReadOnlySet<int> set = [];
Assert.IsType<ReadOnlySet<int>>(set);
Assert.Empty(set);
Assert.Same(set, ReadOnlySet<int>.Empty);
}

[Fact]
public static void Ctor_CollectionExpressions()
{
int[] array = [1, 2, 3, 3, 2, 1];
ReadOnlySet<int> set = [.. array];
Assert.IsType<ReadOnlySet<int>>(set);
Assert.Equal(set.Order(), array.Distinct().Order());
}

[Fact]
public void Empty_EmptyAndIdempotent()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
namespace System.Collections.ObjectModel
{
[Serializable]
[CollectionBuilder(typeof(ReadOnlyCollection), "CreateCollection")]
[DebuggerTypeProxy(typeof(ICollectionDebugView<>))]
[DebuggerDisplay("Count = {Count}")]
[TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
Expand Down Expand Up @@ -229,4 +230,45 @@ void IList.RemoveAt(int index)
ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ReadOnlyCollection);
}
}

/// <summary>
/// Provides static methods for read-only collections.
/// </summary>
public static class ReadOnlyCollection
{
/// <summary>
/// Creates a new <see cref="ReadOnlyCollection{T}"/> from the specified span of values.
/// This method (simplifies collection initialization)[/dotnet/csharp/language-reference/operators/collection-expressions]
/// to create a new <see cref="ReadOnlyCollection{T}"/> with the specified values.
/// </summary>
/// <typeparam name="T">The type of elements in the collection.</typeparam>
/// <param name="values">The span of values to include in the collection.</param>
/// <returns>A new <see cref="ReadOnlyCollection{T}"/> containing the specified values.</returns>
public static ReadOnlyCollection<T> CreateCollection<T>(params ReadOnlySpan<T> values) =>
values.IsEmpty ? ReadOnlyCollection<T>.Empty : new ReadOnlyCollection<T>(values.ToArray());

/// <summary>
/// Creates a new <see cref="ReadOnlySet{T}"/> from the specified span of values.
/// This method (simplifies collection initialization)[/dotnet/csharp/language-reference/operators/collection-expressions]
/// to create a new <see cref="ReadOnlySet{T}"/> with the specified values.
/// </summary>
/// <typeparam name="T">The type of elements in the collection.</typeparam>
/// <param name="values">The span of values to include in the collection.</param>
/// <returns>A new <see cref="ReadOnlySet{T}"/> containing the specified values.</returns>
public static ReadOnlySet<T> CreateSet<T>(params ReadOnlySpan<T> values)
{
if (values.IsEmpty)
{
return ReadOnlySet<T>.Empty;
}

HashSet<T> hashSet = [];
foreach (T value in values)
{
hashSet.Add(value);
}

return new ReadOnlySet<T>(hashSet);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@

using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace System.Collections.ObjectModel
{
/// <summary>Represents a read-only, generic set of values.</summary>
/// <typeparam name="T">The type of values in the set.</typeparam>
[CollectionBuilder(typeof(ReadOnlyCollection), "CreateSet")]
[DebuggerDisplay("Count = {Count}")]
public class ReadOnlySet<T> : IReadOnlySet<T>, ISet<T>, ICollection
{
Expand Down
7 changes: 7 additions & 0 deletions src/libraries/System.Runtime/ref/System.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8366,6 +8366,12 @@ void System.Collections.ICollection.CopyTo(System.Array array, int index) { }
void System.Collections.IList.Insert(int index, object? value) { }
void System.Collections.IList.Remove(object? value) { }
}
public static partial class ReadOnlyCollection
{
public static System.Collections.ObjectModel.ReadOnlyCollection<T> CreateCollection<T>(params System.ReadOnlySpan<T> values) { throw null; }
public static System.Collections.ObjectModel.ReadOnlySet<T> CreateSet<T>(params System.ReadOnlySpan<T> values) { throw null; }
}
[System.Runtime.CompilerServices.CollectionBuilder(typeof(System.Collections.ObjectModel.ReadOnlyCollection), "CreateCollection")]
public partial class ReadOnlyCollection<T> : System.Collections.Generic.ICollection<T>, System.Collections.Generic.IEnumerable<T>, System.Collections.Generic.IList<T>, System.Collections.Generic.IReadOnlyCollection<T>, System.Collections.Generic.IReadOnlyList<T>, System.Collections.ICollection, System.Collections.IEnumerable, System.Collections.IList
{
public ReadOnlyCollection(System.Collections.Generic.IList<T> list) { }
Expand Down Expand Up @@ -8471,6 +8477,7 @@ void System.Collections.ICollection.CopyTo(System.Array array, int index) { }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; }
}
}
[System.Runtime.CompilerServices.CollectionBuilder(typeof(System.Collections.ObjectModel.ReadOnlyCollection), "CreateSet")]
public partial class ReadOnlySet<T> : System.Collections.Generic.ICollection<T>, System.Collections.Generic.IEnumerable<T>, System.Collections.Generic.IReadOnlyCollection<T>, System.Collections.Generic.IReadOnlySet<T>, System.Collections.Generic.ISet<T>, System.Collections.ICollection, System.Collections.IEnumerable
{
public ReadOnlySet(System.Collections.Generic.ISet<T> @set) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,23 @@ public static void Ctor_IList()
Assert.Same(s_intArray, collection.GetItems());
}

[Fact]
public static void Ctor_CollectionExpressions_Empty()
{
ReadOnlyCollection<int> c = [];
Assert.IsType<ReadOnlyCollection<int>>(c);
Assert.Empty(c);
Assert.Same(c, ReadOnlyCollection<int>.Empty);
}

[Fact]
public static void Ctor_CollectionExpressions()
{
ReadOnlyCollection<int> c = [.. s_intArray];
Assert.IsType<ReadOnlyCollection<int>>(c);
Assert.Equal(c, s_intArray);
}

[Fact]
public static void Count()
{
Expand Down

0 comments on commit 22e39f9

Please sign in to comment.