Skip to content

Commit 3916efe

Browse files
authored
Add ReadOnlySet<T> (#103306)
* Add ReadOnlySet<T> * Add test
1 parent a08fe0f commit 3916efe

File tree

5 files changed

+290
-48
lines changed

5 files changed

+290
-48
lines changed

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

+32
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,38 @@ void System.Collections.IEnumerator.Reset() { }
411411
}
412412
}
413413
}
414+
namespace System.Collections.ObjectModel
415+
{
416+
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
417+
{
418+
public ReadOnlySet(System.Collections.Generic.ISet<T> @set) { }
419+
public int Count { get { throw null; } }
420+
public static System.Collections.ObjectModel.ReadOnlySet<T> Empty { get { throw null; } }
421+
protected System.Collections.Generic.ISet<T> Set { get { throw null; } }
422+
bool System.Collections.Generic.ICollection<T>.IsReadOnly { get { throw null; } }
423+
bool System.Collections.ICollection.IsSynchronized { get { throw null; } }
424+
object System.Collections.ICollection.SyncRoot { get { throw null; } }
425+
public bool Contains(T item) { throw null; }
426+
public System.Collections.Generic.IEnumerator<T> GetEnumerator() { throw null; }
427+
public bool IsProperSubsetOf(System.Collections.Generic.IEnumerable<T> other) { throw null; }
428+
public bool IsProperSupersetOf(System.Collections.Generic.IEnumerable<T> other) { throw null; }
429+
public bool IsSubsetOf(System.Collections.Generic.IEnumerable<T> other) { throw null; }
430+
public bool IsSupersetOf(System.Collections.Generic.IEnumerable<T> other) { throw null; }
431+
public bool Overlaps(System.Collections.Generic.IEnumerable<T> other) { throw null; }
432+
public bool SetEquals(System.Collections.Generic.IEnumerable<T> other) { throw null; }
433+
void System.Collections.Generic.ICollection<T>.Add(T item) { }
434+
void System.Collections.Generic.ICollection<T>.Clear() { }
435+
void System.Collections.Generic.ICollection<T>.CopyTo(T[] array, int arrayIndex) { }
436+
bool System.Collections.Generic.ICollection<T>.Remove(T item) { throw null; }
437+
bool System.Collections.Generic.ISet<T>.Add(T item) { throw null; }
438+
void System.Collections.Generic.ISet<T>.ExceptWith(System.Collections.Generic.IEnumerable<T> other) { }
439+
void System.Collections.Generic.ISet<T>.IntersectWith(System.Collections.Generic.IEnumerable<T> other) { }
440+
void System.Collections.Generic.ISet<T>.SymmetricExceptWith(System.Collections.Generic.IEnumerable<T> other) { }
441+
void System.Collections.Generic.ISet<T>.UnionWith(System.Collections.Generic.IEnumerable<T> other) { }
442+
void System.Collections.ICollection.CopyTo(System.Array array, int index) { }
443+
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; }
444+
}
445+
}
414446
#endif // !BUILDING_CORELIB_REFERENCE
415447
namespace System.Collections.Generic
416448
{

src/libraries/System.Collections/src/System.Collections.csproj

+3
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@
1616
Link="Common\System\Collections\Generic\ICollectionDebugView.cs" />
1717
<Compile Include="$(CoreLibSharedDir)System\Collections\Generic\IDictionaryDebugView.cs"
1818
Link="Common\System\Collections\Generic\IDictionaryDebugView.cs" />
19+
<Compile Include="$(CoreLibSharedDir)System\Collections\ObjectModel\CollectionHelpers.cs"
20+
Link="Common\System\Collections\ObjectModel\CollectionHelpers.cs" />
1921
<Compile Include="System\Collections\Generic\LinkedList.cs" />
2022
<Compile Include="System\Collections\Generic\PriorityQueue.cs" />
2123
<Compile Include="System\Collections\Generic\PriorityQueueDebugView.cs" />
24+
<Compile Include="System\Collections\Generic\ReadOnlySet.cs" />
2225
<Compile Include="System\Collections\Generic\SortedDictionary.cs" />
2326
<Compile Include="System\Collections\Generic\SortedList.cs" />
2427
<Compile Include="System\Collections\Generic\SortedSet.cs" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.Generic;
5+
using System.Diagnostics;
6+
7+
namespace System.Collections.ObjectModel
8+
{
9+
/// <summary>Represents a read-only, generic set of values.</summary>
10+
/// <typeparam name="T">The type of values in the set.</typeparam>
11+
[DebuggerDisplay("Count = {Count}")]
12+
public class ReadOnlySet<T> : IReadOnlySet<T>, ISet<T>, ICollection
13+
{
14+
/// <summary>The wrapped set.</summary>
15+
private readonly ISet<T> _set;
16+
17+
/// <summary>Initializes a new instance of the <see cref="ReadOnlySet{T}"/> class that is a wrapper around the specified set.</summary>
18+
/// <param name="set">The set to wrap.</param>
19+
public ReadOnlySet(ISet<T> set)
20+
{
21+
ArgumentNullException.ThrowIfNull(set);
22+
_set = set;
23+
}
24+
25+
/// <summary>Gets an empty <see cref="ReadOnlySet{T}"/>.</summary>
26+
public static ReadOnlySet<T> Empty { get; } = new ReadOnlySet<T>(new HashSet<T>());
27+
28+
/// <summary>Gets the set that is wrapped by this <see cref="ReadOnlySet{T}"/> object.</summary>
29+
protected ISet<T> Set => _set;
30+
31+
/// <inheritdoc/>
32+
public int Count => _set.Count;
33+
34+
/// <inheritdoc/>
35+
public IEnumerator<T> GetEnumerator() =>
36+
_set.Count == 0 ? ((IEnumerable<T>)Array.Empty<T>()).GetEnumerator() :
37+
_set.GetEnumerator();
38+
39+
/// <inheritdoc/>
40+
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
41+
42+
/// <inheritdoc/>
43+
public bool Contains(T item) => _set.Contains(item);
44+
45+
/// <inheritdoc/>
46+
public bool IsProperSubsetOf(IEnumerable<T> other) => _set.IsProperSubsetOf(other);
47+
48+
/// <inheritdoc/>
49+
public bool IsProperSupersetOf(IEnumerable<T> other) => _set.IsProperSupersetOf(other);
50+
51+
/// <inheritdoc/>
52+
public bool IsSubsetOf(IEnumerable<T> other) => _set.IsSubsetOf(other);
53+
54+
/// <inheritdoc/>
55+
public bool IsSupersetOf(IEnumerable<T> other) => _set.IsSupersetOf(other);
56+
57+
/// <inheritdoc/>
58+
public bool Overlaps(IEnumerable<T> other) => _set.Overlaps(other);
59+
60+
/// <inheritdoc/>
61+
public bool SetEquals(IEnumerable<T> other) => _set.SetEquals(other);
62+
63+
/// <inheritdoc/>
64+
void ICollection<T>.CopyTo(T[] array, int arrayIndex) => _set.CopyTo(array, arrayIndex);
65+
66+
/// <inheritdoc/>
67+
void ICollection.CopyTo(Array array, int index) => CollectionHelpers.CopyTo(_set, array, index);
68+
69+
/// <inheritdoc/>
70+
bool ICollection<T>.IsReadOnly => true;
71+
72+
/// <inheritdoc/>
73+
bool ICollection.IsSynchronized => false;
74+
75+
/// <inheritdoc/>
76+
object ICollection.SyncRoot => _set is ICollection c ? c.SyncRoot : this;
77+
78+
/// <inheritdoc/>
79+
bool ISet<T>.Add(T item) => throw new NotSupportedException();
80+
81+
/// <inheritdoc/>
82+
void ISet<T>.ExceptWith(IEnumerable<T> other) => throw new NotSupportedException();
83+
84+
/// <inheritdoc/>
85+
void ISet<T>.IntersectWith(IEnumerable<T> other) => throw new NotSupportedException();
86+
87+
/// <inheritdoc/>
88+
void ISet<T>.SymmetricExceptWith(IEnumerable<T> other) => throw new NotSupportedException();
89+
90+
/// <inheritdoc/>
91+
void ISet<T>.UnionWith(IEnumerable<T> other) => throw new NotSupportedException();
92+
93+
/// <inheritdoc/>
94+
void ICollection<T>.Add(T item) => throw new NotSupportedException();
95+
96+
/// <inheritdoc/>
97+
void ICollection<T>.Clear() => throw new NotSupportedException();
98+
99+
/// <inheritdoc/>
100+
bool ICollection<T>.Remove(T item) => throw new NotSupportedException();
101+
}
102+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.Generic;
5+
using Xunit;
6+
7+
namespace System.Collections.ObjectModel.Tests
8+
{
9+
public class ReadOnlySetTests
10+
{
11+
[Fact]
12+
public void Ctor_NullSet_ThrowsArgumentNullException()
13+
{
14+
AssertExtensions.Throws<ArgumentNullException>("set", () => new ReadOnlySet<int>(null));
15+
}
16+
17+
[Fact]
18+
public void Ctor_SetProperty_Roundtrips()
19+
{
20+
var set = new HashSet<int>();
21+
Assert.Same(set, new DerivedReadOnlySet<int>(set).Set);
22+
}
23+
24+
[Fact]
25+
public void Empty_EmptyAndIdempotent()
26+
{
27+
Assert.Same(ReadOnlySet<int>.Empty, ReadOnlySet<int>.Empty);
28+
Assert.Empty(ReadOnlySet<int>.Empty);
29+
Assert.Same(ReadOnlySet<int>.Empty.GetEnumerator(), ReadOnlySet<int>.Empty.GetEnumerator());
30+
}
31+
32+
[Fact]
33+
public void MembersDelegateToWrappedSet()
34+
{
35+
var set = new ReadOnlySet<int>(new HashSet<int>() { 1, 2, 3 });
36+
37+
Assert.True(set.Contains(2));
38+
Assert.False(set.Contains(4));
39+
40+
Assert.Equal(3, set.Count);
41+
42+
Assert.True(set.IsProperSubsetOf([1, 2, 3, 4]));
43+
Assert.False(set.IsProperSubsetOf([1, 2, 5]));
44+
45+
Assert.True(set.IsProperSupersetOf([1, 2]));
46+
Assert.False(set.IsProperSupersetOf([1, 4]));
47+
48+
Assert.True(set.IsSubsetOf([1, 2, 3, 4]));
49+
Assert.False(set.IsSubsetOf([1, 2, 5]));
50+
51+
Assert.True(set.IsSupersetOf([1, 2]));
52+
Assert.False(set.IsSupersetOf([1, 4]));
53+
54+
Assert.True(set.Overlaps([-1, 0, 1]));
55+
Assert.False(set.Overlaps([-1, 0]));
56+
57+
Assert.True(set.SetEquals([1, 2, 3]));
58+
Assert.False(set.SetEquals([1, 2, 4]));
59+
60+
int[] result = new int[3];
61+
((ICollection<int>)set).CopyTo(result, 0);
62+
Assert.Equal(result, new int[] { 1, 2, 3 });
63+
64+
Array.Clear(result);
65+
((ICollection)set).CopyTo(result, 0);
66+
Assert.Equal(result, new int[] { 1, 2, 3 });
67+
68+
Assert.NotNull(set.GetEnumerator());
69+
}
70+
71+
[Fact]
72+
public void ChangesToUnderlyingSetReflected()
73+
{
74+
var set = new HashSet<int> { 1, 2, 3 };
75+
var readOnlySet = new ReadOnlySet<int>(set);
76+
77+
set.Add(4);
78+
Assert.Equal(4, readOnlySet.Count);
79+
Assert.True(readOnlySet.Contains(4));
80+
81+
set.Remove(2);
82+
Assert.Equal(3, readOnlySet.Count);
83+
Assert.False(readOnlySet.Contains(2));
84+
}
85+
86+
[Fact]
87+
public void IsReadOnly_True()
88+
{
89+
var set = new ReadOnlySet<int>(new HashSet<int> { 1, 2, 3 });
90+
Assert.True(((ICollection<int>)set).IsReadOnly);
91+
}
92+
93+
[Fact]
94+
public void MutationThrows_CollectionUnmodified()
95+
{
96+
var set = new HashSet<int> { 1, 2, 3 };
97+
var readOnlySet = new ReadOnlySet<int>(set);
98+
99+
Assert.Throws<NotSupportedException>(() => ((ICollection<int>)readOnlySet).Add(4));
100+
Assert.Throws<NotSupportedException>(() => ((ICollection<int>)readOnlySet).Remove(1));
101+
Assert.Throws<NotSupportedException>(() => ((ICollection<int>)readOnlySet).Clear());
102+
103+
Assert.Throws<NotSupportedException>(() => ((ISet<int>)readOnlySet).Add(4));
104+
Assert.Throws<NotSupportedException>(() => ((ISet<int>)readOnlySet).ExceptWith([1, 2, 3]));
105+
Assert.Throws<NotSupportedException>(() => ((ISet<int>)readOnlySet).IntersectWith([1, 2, 3]));
106+
Assert.Throws<NotSupportedException>(() => ((ISet<int>)readOnlySet).SymmetricExceptWith([1, 2, 3]));
107+
Assert.Throws<NotSupportedException>(() => ((ISet<int>)readOnlySet).UnionWith([1, 2, 3]));
108+
109+
Assert.Equal(3, set.Count);
110+
}
111+
112+
[Fact]
113+
public void ICollection_Synchronization()
114+
{
115+
var set = new ReadOnlySet<int>(new HashSet<int> { 1, 2, 3 });
116+
117+
Assert.False(((ICollection)set).IsSynchronized);
118+
Assert.Same(set, ((ICollection)set).SyncRoot);
119+
}
120+
121+
private class DerivedReadOnlySet<T> : ReadOnlySet<T>
122+
{
123+
public DerivedReadOnlySet(HashSet<T> set) : base(set) { }
124+
125+
public new ISet<T> Set => base.Set;
126+
}
127+
}
128+
}

0 commit comments

Comments
 (0)