diff --git a/src/libraries/System.Collections/ref/System.Collections.cs b/src/libraries/System.Collections/ref/System.Collections.cs index 17420dfca8dc68..70f4abe1a4bf0d 100644 --- a/src/libraries/System.Collections/ref/System.Collections.cs +++ b/src/libraries/System.Collections/ref/System.Collections.cs @@ -411,6 +411,38 @@ void System.Collections.IEnumerator.Reset() { } } } } +namespace System.Collections.ObjectModel +{ + public partial class ReadOnlySet : System.Collections.Generic.ICollection, System.Collections.Generic.IEnumerable, System.Collections.Generic.IReadOnlyCollection, System.Collections.Generic.IReadOnlySet, System.Collections.Generic.ISet, System.Collections.ICollection, System.Collections.IEnumerable + { + public ReadOnlySet(System.Collections.Generic.ISet @set) { } + public int Count { get { throw null; } } + public static System.Collections.ObjectModel.ReadOnlySet Empty { get { throw null; } } + protected System.Collections.Generic.ISet Set { get { throw null; } } + bool System.Collections.Generic.ICollection.IsReadOnly { get { throw null; } } + bool System.Collections.ICollection.IsSynchronized { get { throw null; } } + object System.Collections.ICollection.SyncRoot { get { throw null; } } + public bool Contains(T item) { throw null; } + public System.Collections.Generic.IEnumerator GetEnumerator() { throw null; } + public bool IsProperSubsetOf(System.Collections.Generic.IEnumerable other) { throw null; } + public bool IsProperSupersetOf(System.Collections.Generic.IEnumerable other) { throw null; } + public bool IsSubsetOf(System.Collections.Generic.IEnumerable other) { throw null; } + public bool IsSupersetOf(System.Collections.Generic.IEnumerable other) { throw null; } + public bool Overlaps(System.Collections.Generic.IEnumerable other) { throw null; } + public bool SetEquals(System.Collections.Generic.IEnumerable other) { throw null; } + void System.Collections.Generic.ICollection.Add(T item) { } + void System.Collections.Generic.ICollection.Clear() { } + void System.Collections.Generic.ICollection.CopyTo(T[] array, int arrayIndex) { } + bool System.Collections.Generic.ICollection.Remove(T item) { throw null; } + bool System.Collections.Generic.ISet.Add(T item) { throw null; } + void System.Collections.Generic.ISet.ExceptWith(System.Collections.Generic.IEnumerable other) { } + void System.Collections.Generic.ISet.IntersectWith(System.Collections.Generic.IEnumerable other) { } + void System.Collections.Generic.ISet.SymmetricExceptWith(System.Collections.Generic.IEnumerable other) { } + void System.Collections.Generic.ISet.UnionWith(System.Collections.Generic.IEnumerable other) { } + void System.Collections.ICollection.CopyTo(System.Array array, int index) { } + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } + } +} #endif // !BUILDING_CORELIB_REFERENCE namespace System.Collections.Generic { diff --git a/src/libraries/System.Collections/src/System.Collections.csproj b/src/libraries/System.Collections/src/System.Collections.csproj index 0f55489ea005e3..871c508e84104a 100644 --- a/src/libraries/System.Collections/src/System.Collections.csproj +++ b/src/libraries/System.Collections/src/System.Collections.csproj @@ -16,9 +16,12 @@ Link="Common\System\Collections\Generic\ICollectionDebugView.cs" /> + + diff --git a/src/libraries/System.Collections/src/System/Collections/Generic/ReadOnlySet.cs b/src/libraries/System.Collections/src/System/Collections/Generic/ReadOnlySet.cs new file mode 100644 index 00000000000000..c9b26b24deeaa0 --- /dev/null +++ b/src/libraries/System.Collections/src/System/Collections/Generic/ReadOnlySet.cs @@ -0,0 +1,102 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Diagnostics; + +namespace System.Collections.ObjectModel +{ + /// Represents a read-only, generic set of values. + /// The type of values in the set. + [DebuggerDisplay("Count = {Count}")] + public class ReadOnlySet : IReadOnlySet, ISet, ICollection + { + /// The wrapped set. + private readonly ISet _set; + + /// Initializes a new instance of the class that is a wrapper around the specified set. + /// The set to wrap. + public ReadOnlySet(ISet set) + { + ArgumentNullException.ThrowIfNull(set); + _set = set; + } + + /// Gets an empty . + public static ReadOnlySet Empty { get; } = new ReadOnlySet(new HashSet()); + + /// Gets the set that is wrapped by this object. + protected ISet Set => _set; + + /// + public int Count => _set.Count; + + /// + public IEnumerator GetEnumerator() => + _set.Count == 0 ? ((IEnumerable)Array.Empty()).GetEnumerator() : + _set.GetEnumerator(); + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + public bool Contains(T item) => _set.Contains(item); + + /// + public bool IsProperSubsetOf(IEnumerable other) => _set.IsProperSubsetOf(other); + + /// + public bool IsProperSupersetOf(IEnumerable other) => _set.IsProperSupersetOf(other); + + /// + public bool IsSubsetOf(IEnumerable other) => _set.IsSubsetOf(other); + + /// + public bool IsSupersetOf(IEnumerable other) => _set.IsSupersetOf(other); + + /// + public bool Overlaps(IEnumerable other) => _set.Overlaps(other); + + /// + public bool SetEquals(IEnumerable other) => _set.SetEquals(other); + + /// + void ICollection.CopyTo(T[] array, int arrayIndex) => _set.CopyTo(array, arrayIndex); + + /// + void ICollection.CopyTo(Array array, int index) => CollectionHelpers.CopyTo(_set, array, index); + + /// + bool ICollection.IsReadOnly => true; + + /// + bool ICollection.IsSynchronized => false; + + /// + object ICollection.SyncRoot => _set is ICollection c ? c.SyncRoot : this; + + /// + bool ISet.Add(T item) => throw new NotSupportedException(); + + /// + void ISet.ExceptWith(IEnumerable other) => throw new NotSupportedException(); + + /// + void ISet.IntersectWith(IEnumerable other) => throw new NotSupportedException(); + + /// + void ISet.SymmetricExceptWith(IEnumerable other) => throw new NotSupportedException(); + + /// + void ISet.UnionWith(IEnumerable other) => throw new NotSupportedException(); + + /// + void ICollection.Add(T item) => throw new NotSupportedException(); + + /// + void ICollection.Clear() => throw new NotSupportedException(); + + /// + bool ICollection.Remove(T item) => throw new NotSupportedException(); + } +} diff --git a/src/libraries/System.Collections/tests/Generic/ReadOnlySet/ReadOnlySetTests.cs b/src/libraries/System.Collections/tests/Generic/ReadOnlySet/ReadOnlySetTests.cs new file mode 100644 index 00000000000000..0080ceb603c7e6 --- /dev/null +++ b/src/libraries/System.Collections/tests/Generic/ReadOnlySet/ReadOnlySetTests.cs @@ -0,0 +1,128 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using Xunit; + +namespace System.Collections.ObjectModel.Tests +{ + public class ReadOnlySetTests + { + [Fact] + public void Ctor_NullSet_ThrowsArgumentNullException() + { + AssertExtensions.Throws("set", () => new ReadOnlySet(null)); + } + + [Fact] + public void Ctor_SetProperty_Roundtrips() + { + var set = new HashSet(); + Assert.Same(set, new DerivedReadOnlySet(set).Set); + } + + [Fact] + public void Empty_EmptyAndIdempotent() + { + Assert.Same(ReadOnlySet.Empty, ReadOnlySet.Empty); + Assert.Empty(ReadOnlySet.Empty); + Assert.Same(ReadOnlySet.Empty.GetEnumerator(), ReadOnlySet.Empty.GetEnumerator()); + } + + [Fact] + public void MembersDelegateToWrappedSet() + { + var set = new ReadOnlySet(new HashSet() { 1, 2, 3 }); + + Assert.True(set.Contains(2)); + Assert.False(set.Contains(4)); + + Assert.Equal(3, set.Count); + + Assert.True(set.IsProperSubsetOf([1, 2, 3, 4])); + Assert.False(set.IsProperSubsetOf([1, 2, 5])); + + Assert.True(set.IsProperSupersetOf([1, 2])); + Assert.False(set.IsProperSupersetOf([1, 4])); + + Assert.True(set.IsSubsetOf([1, 2, 3, 4])); + Assert.False(set.IsSubsetOf([1, 2, 5])); + + Assert.True(set.IsSupersetOf([1, 2])); + Assert.False(set.IsSupersetOf([1, 4])); + + Assert.True(set.Overlaps([-1, 0, 1])); + Assert.False(set.Overlaps([-1, 0])); + + Assert.True(set.SetEquals([1, 2, 3])); + Assert.False(set.SetEquals([1, 2, 4])); + + int[] result = new int[3]; + ((ICollection)set).CopyTo(result, 0); + Assert.Equal(result, new int[] { 1, 2, 3 }); + + Array.Clear(result); + ((ICollection)set).CopyTo(result, 0); + Assert.Equal(result, new int[] { 1, 2, 3 }); + + Assert.NotNull(set.GetEnumerator()); + } + + [Fact] + public void ChangesToUnderlyingSetReflected() + { + var set = new HashSet { 1, 2, 3 }; + var readOnlySet = new ReadOnlySet(set); + + set.Add(4); + Assert.Equal(4, readOnlySet.Count); + Assert.True(readOnlySet.Contains(4)); + + set.Remove(2); + Assert.Equal(3, readOnlySet.Count); + Assert.False(readOnlySet.Contains(2)); + } + + [Fact] + public void IsReadOnly_True() + { + var set = new ReadOnlySet(new HashSet { 1, 2, 3 }); + Assert.True(((ICollection)set).IsReadOnly); + } + + [Fact] + public void MutationThrows_CollectionUnmodified() + { + var set = new HashSet { 1, 2, 3 }; + var readOnlySet = new ReadOnlySet(set); + + Assert.Throws(() => ((ICollection)readOnlySet).Add(4)); + Assert.Throws(() => ((ICollection)readOnlySet).Remove(1)); + Assert.Throws(() => ((ICollection)readOnlySet).Clear()); + + Assert.Throws(() => ((ISet)readOnlySet).Add(4)); + Assert.Throws(() => ((ISet)readOnlySet).ExceptWith([1, 2, 3])); + Assert.Throws(() => ((ISet)readOnlySet).IntersectWith([1, 2, 3])); + Assert.Throws(() => ((ISet)readOnlySet).SymmetricExceptWith([1, 2, 3])); + Assert.Throws(() => ((ISet)readOnlySet).UnionWith([1, 2, 3])); + + Assert.Equal(3, set.Count); + } + + [Fact] + public void ICollection_Synchronization() + { + var set = new ReadOnlySet(new HashSet { 1, 2, 3 }); + + Assert.False(((ICollection)set).IsSynchronized); + Assert.Same(set, ((ICollection)set).SyncRoot); + } + + private class DerivedReadOnlySet : ReadOnlySet + { + public DerivedReadOnlySet(HashSet set) : base(set) { } + + public new ISet Set => base.Set; + } + } +} diff --git a/src/libraries/System.Collections/tests/System.Collections.Tests.csproj b/src/libraries/System.Collections/tests/System.Collections.Tests.csproj index 1f45aa68f98f6e..ab0bc732cda1b2 100644 --- a/src/libraries/System.Collections/tests/System.Collections.Tests.csproj +++ b/src/libraries/System.Collections/tests/System.Collections.Tests.csproj @@ -9,44 +9,26 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + @@ -126,16 +108,11 @@ - + - - - - + + + +