diff --git a/src/libraries/System.Collections/ref/System.Collections.cs b/src/libraries/System.Collections/ref/System.Collections.cs index c12b04c3a3cf07..2fa8f622b611f6 100644 --- a/src/libraries/System.Collections/ref/System.Collections.cs +++ b/src/libraries/System.Collections/ref/System.Collections.cs @@ -379,6 +379,7 @@ public Stack() { } public Stack(System.Collections.Generic.IEnumerable collection) { } public Stack(int capacity) { } public int Count { get { throw null; } } + public int Capacity { get { throw null; } } bool System.Collections.ICollection.IsSynchronized { get { throw null; } } object System.Collections.ICollection.SyncRoot { get { throw null; } } public void Clear() { } @@ -394,6 +395,7 @@ void System.Collections.ICollection.CopyTo(System.Array array, int arrayIndex) { public int EnsureCapacity(int capacity) { throw null; } public T[] ToArray() { throw null; } public void TrimExcess() { } + public void TrimExcess(int capacity) { } public bool TryPeek([System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out T result) { throw null; } public bool TryPop([System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out T result) { throw null; } public partial struct Enumerator : System.Collections.Generic.IEnumerator, System.Collections.IEnumerator, System.IDisposable @@ -447,6 +449,7 @@ public Dictionary(int capacity, System.Collections.Generic.IEqualityComparer Comparer { get { throw null; } } public int Count { get { throw null; } } + public int Capacity { get { throw null; } } public TValue this[TKey key] { get { throw null; } set { } } public System.Collections.Generic.Dictionary.KeyCollection Keys { get { throw null; } } bool System.Collections.Generic.ICollection>.IsReadOnly { get { throw null; } } @@ -582,6 +585,7 @@ public HashSet(int capacity, System.Collections.Generic.IEqualityComparer? co protected HashSet(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } public System.Collections.Generic.IEqualityComparer Comparer { get { throw null; } } public int Count { get { throw null; } } + public int Capacity { get { throw null; } } bool System.Collections.Generic.ICollection.IsReadOnly { get { throw null; } } public bool Add(T item) { throw null; } public void Clear() { } @@ -611,6 +615,7 @@ void System.Collections.Generic.ICollection.Add(T item) { } System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator() { throw null; } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } public void TrimExcess() { } + public void TrimExcess(int capacity) { } public bool TryGetValue(T equalValue, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out T actualValue) { throw null; } public void UnionWith(System.Collections.Generic.IEnumerable other) { } public partial struct Enumerator : System.Collections.Generic.IEnumerator, System.Collections.IEnumerator, System.IDisposable @@ -713,6 +718,7 @@ public Queue() { } public Queue(System.Collections.Generic.IEnumerable collection) { } public Queue(int capacity) { } public int Count { get { throw null; } } + public int Capacity { get { throw null; } } bool System.Collections.ICollection.IsSynchronized { get { throw null; } } object System.Collections.ICollection.SyncRoot { get { throw null; } } public void Clear() { } @@ -727,6 +733,7 @@ void System.Collections.ICollection.CopyTo(System.Array array, int index) { } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } public T[] ToArray() { throw null; } public void TrimExcess() { } + public void TrimExcess(int capacity) { } public int EnsureCapacity(int capacity) { throw null; } public bool TryDequeue([System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out T result) { throw null; } public bool TryPeek([System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out T result) { throw null; } diff --git a/src/libraries/System.Collections/src/System/Collections/Generic/Stack.cs b/src/libraries/System.Collections/src/System/Collections/Generic/Stack.cs index 2bc209c6a01e0b..1317ed240c06a3 100644 --- a/src/libraries/System.Collections/src/System/Collections/Generic/Stack.cs +++ b/src/libraries/System.Collections/src/System/Collections/Generic/Stack.cs @@ -54,15 +54,16 @@ public Stack(IEnumerable collection) _array = EnumerableHelpers.ToArray(collection, out _size); } - public int Count - { - get { return _size; } - } + public int Count => _size; - bool ICollection.IsSynchronized - { - get { return false; } - } + + /// + /// Gets the total numbers of elements the internal data structure can hold without resizing. + /// + public int Capacity => _array.Length; + + /// + bool ICollection.IsSynchronized => false; object ICollection.SyncRoot => this; @@ -170,6 +171,22 @@ public void TrimExcess() } } + /// + /// Sets the capacity of a object to a specified number of entries. + /// + /// The new capacity. + /// Passed capacity is lower than 0 or entries count. + public void TrimExcess(int capacity) + { + ArgumentOutOfRangeException.ThrowIfNegative(capacity); + ArgumentOutOfRangeException.ThrowIfLessThan(capacity, _size); + + if (capacity == _array.Length) + return; + + Array.Resize(ref _array, capacity); + } + // Returns the top object on the stack without removing it. If the stack // is empty, Peek throws an InvalidOperationException. public T Peek() diff --git a/src/libraries/System.Collections/tests/Generic/Dictionary/Dictionary.Generic.Tests.cs b/src/libraries/System.Collections/tests/Generic/Dictionary/Dictionary.Generic.Tests.cs index fa12ef55d6c540..aa6133538895d4 100644 --- a/src/libraries/System.Collections/tests/Generic/Dictionary/Dictionary.Generic.Tests.cs +++ b/src/libraries/System.Collections/tests/Generic/Dictionary/Dictionary.Generic.Tests.cs @@ -83,8 +83,36 @@ public void Dictionary_Generic_Constructor_int_IEqualityComparer(int count) Assert.Equal(comparer, dictionary.Comparer); } + [Theory] + [InlineData(1)] + [InlineData(100)] + public void Dictionary_CreateWithCapacity_CapacityAtLeastPassedValue(int capacity) + { + Dictionary dict = new Dictionary(capacity); + Assert.True(capacity <= dict.Capacity); + } + #endregion + #region Properties + + public void DictResized_CapacityChanged() + { + var dict = (Dictionary)GenericIDictionaryFactory(1); + int initialCapacity = dict.Capacity; + + int seed = 85877; + for (int i = 0; i < dict.Capacity; i++) + { + dict.Add(CreateTKey(seed++), CreateTValue(seed++)); + } + + int afterCapacity = dict.Capacity; + + Assert.True(afterCapacity > initialCapacity); + } + + #endregion #region ContainsValue [Theory] diff --git a/src/libraries/System.Collections/tests/Generic/HashSet/HashSet.Generic.Tests.cs b/src/libraries/System.Collections/tests/Generic/HashSet/HashSet.Generic.Tests.cs index afbcad3c357cf6..038bbbdb960c3f 100644 --- a/src/libraries/System.Collections/tests/Generic/HashSet/HashSet.Generic.Tests.cs +++ b/src/libraries/System.Collections/tests/Generic/HashSet/HashSet.Generic.Tests.cs @@ -126,6 +126,33 @@ public void HashSet_Generic_Constructor_IEnumerable_IEqualityComparer(Enumerable Assert.True(set.SetEquals(enumerable)); } + [Theory] + [InlineData(1)] + [InlineData(100)] + public void HashSet_CreateWithCapacity_CapacityAtLeastPassedValue(int capacity) + { + var hashSet = new HashSet(capacity); + Assert.True(capacity <= hashSet.Capacity); + } + + #endregion + + #region Properties + + [Fact] + public void HashSetResized_CapacityChanged() + { + var hashSet = (HashSet)GenericISetFactory(3); + int initialCapacity = hashSet.Capacity; + + int seed = 85877; + hashSet.Add(CreateT(seed++)); + + int afterCapacity = hashSet.Capacity; + + Assert.True(afterCapacity > initialCapacity); + } + #endregion #region RemoveWhere @@ -175,6 +202,24 @@ public void HashSet_Generic_RemoveWhere_NullMatchPredicate(int setLength) #region TrimExcess + [Theory] + [InlineData(1, -1)] + [InlineData(2, 1)] + public void HashSet_TrimAccessWithInvalidArg_ThrowOutOfRange(int size, int newCapacity) + { + HashSet hashSet = (HashSet)GenericISetFactory(size); + + AssertExtensions.Throws(() => hashSet.TrimExcess(newCapacity)); + } + + [Fact] + public void TrimExcess_Generic_LargeInitialCapacity_TrimReducesSize() + { + var set = new HashSet(20); + set.TrimExcess(7); + Assert.Equal(7, set.Capacity); + } + [Theory] [MemberData(nameof(ValidCollectionSizes))] public void HashSet_Generic_TrimExcess_OnValidSetThatHasntBeenRemovedFrom(int setLength) diff --git a/src/libraries/System.Collections/tests/Generic/Queue/Queue.Generic.Tests.cs b/src/libraries/System.Collections/tests/Generic/Queue/Queue.Generic.Tests.cs index 3fd069023ca253..7c900d4c6ef325 100644 --- a/src/libraries/System.Collections/tests/Generic/Queue/Queue.Generic.Tests.cs +++ b/src/libraries/System.Collections/tests/Generic/Queue/Queue.Generic.Tests.cs @@ -21,9 +21,9 @@ protected Queue GenericQueueFactory() return new Queue(); } - protected Queue GenericQueueFactory(int count) + protected Queue GenericQueueFactory(int count, int? capacity = null) { - Queue queue = new Queue(count); + Queue queue = new Queue(capacity ?? count); int seed = count * 34; for (int i = 0; i < count; i++) queue.Enqueue(CreateT(seed++)); @@ -97,6 +97,15 @@ public void Queue_Generic_Constructor_int_Negative_ThrowsArgumentOutOfRangeExcep AssertExtensions.Throws("capacity", () => new Queue(int.MinValue)); } + [Theory] + [InlineData(1)] + [InlineData(100)] + public void Queue_CreateWithCapacity_EqualsCapacityProperty(int capacity) + { + var queue = new Queue(capacity); + Assert.Equal(capacity, queue.Capacity); + } + #endregion #region Dequeue @@ -203,6 +212,34 @@ public void Queue_Generic_Peek_OnEmptyQueue_ThrowsInvalidOperationException() #region TrimExcess + [Theory] + [InlineData(1, -1)] + [InlineData(2, 1)] + public void Queue_TrimAccessWithInvalidArg_ThrowOutOfRange(int size, int newCapacity) + { + Queue queue = GenericQueueFactory(size); + + AssertExtensions.Throws(() => queue.TrimExcess(newCapacity)); + } + + [Fact] + public void Queue_TrimAccessCurrentCount_ReducesToCount() + { + var queue = GenericQueueFactory(20, capacity: 30); + Assert.Equal(30, queue.Capacity); + Assert.Equal(20, queue.Count); + + queue.TrimExcess(queue.Count); + + Assert.Equal(20, queue.Capacity); + Assert.Equal(20, queue.Count); + + queue.TrimExcess(queue.Count); + + Assert.Equal(20, queue.Capacity); + Assert.Equal(20, queue.Count); + } + [Theory] [MemberData(nameof(ValidCollectionSizes))] public void Queue_Generic_TrimExcess_OnValidQueueThatHasntBeenRemovedFrom(int count) @@ -466,5 +503,17 @@ public void Queue_Generic_EnsureCapacity_RequestingLargerCapacity_DoesNotImpactQ Assert.Equal(copiedList[i], queue.Dequeue()); } } + + [Fact] + public void QueueResized_CapacityUpdates() + { + var queue = GenericQueueFactory(1); + + int initialCapacity = queue.Capacity; + + queue.Enqueue(CreateT(85877)); + + Assert.True(initialCapacity < queue.Capacity); + } } } diff --git a/src/libraries/System.Collections/tests/Generic/Stack/Stack.Generic.Tests.cs b/src/libraries/System.Collections/tests/Generic/Stack/Stack.Generic.Tests.cs index 4c5c5a49d243e1..a3e04713f1d6aa 100644 --- a/src/libraries/System.Collections/tests/Generic/Stack/Stack.Generic.Tests.cs +++ b/src/libraries/System.Collections/tests/Generic/Stack/Stack.Generic.Tests.cs @@ -108,6 +108,15 @@ public void Stack_Generic_Constructor_int_Negative_ThrowsArgumentOutOfRangeExcep AssertExtensions.Throws("capacity", () => new Stack(int.MinValue)); } + [Theory] + [InlineData(1)] + [InlineData(100)] + public void Stack_CreateWithCapacity_EqualsCapacityProperty(int capacity) + { + var stack = new Stack(capacity); + Assert.Equal(capacity, stack.Capacity); + } + #endregion #region Pop @@ -167,6 +176,27 @@ public void Stack_Generic_Peek_OnEmptyStack_ThrowsInvalidOperationException() #region TrimExcess + [Theory] + [InlineData(1, -1)] + [InlineData(2, 1)] + public void Stack_TrimAccessWithInvalidArg_ThrowOutOfRange(int size, int newCapacity) + { + Stack stack = GenericStackFactory(size); + + AssertExtensions.Throws(() => stack.TrimExcess(newCapacity)); + } + + [Fact] + public void Stack_TrimAccessCurrentCount_DoesNothing() + { + var stack = GenericStackFactory(10); + stack.TrimExcess(stack.Count); + int capacity = stack.Capacity; + stack.TrimExcess(stack.Count); + + Assert.Equal(capacity, stack.Capacity); + } + [Theory] [MemberData(nameof(ValidCollectionSizes))] public void Stack_Generic_TrimExcess_OnValidStackThatHasntBeenRemovedFrom(int count) @@ -395,5 +425,16 @@ public void Stack_Generic_EnsureCapacity_RequestingLargerCapacity_DoesNotImpactS Assert.Equal(copiedList[i], stack.Pop()); } } + + [Fact] + public void StackResized_CapacityUpdates() + { + Stack stack = GenericStackFactory(10); + int initialCapacity = stack.Capacity; + + stack.Push(CreateT(85877)); + + Assert.True(initialCapacity < stack.Capacity); + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs index 39770356157442..6007efe778ce21 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs @@ -208,6 +208,11 @@ public IEqualityComparer Comparer public int Count => _count - _freeCount; + /// + /// Gets the total numbers of elements the internal data structure can hold without resizing. + /// + public int Capacity => _entries?.Length ?? 0; + public KeyCollection Keys => _keys ??= new KeyCollection(this); ICollection IDictionary.Keys => Keys; @@ -1180,6 +1185,7 @@ public int EnsureCapacity(int capacity) /// This method can be used to minimize the memory overhead /// once it is known that no new elements will be added. /// + /// Passed capacity is lower than entries count. public void TrimExcess(int capacity) { if (capacity < Count) diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/HashSet.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/HashSet.cs index 668550bddf66b5..460e6a8940d6a9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/HashSet.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/HashSet.cs @@ -353,6 +353,11 @@ public bool Remove(T item) /// Gets the number of elements that are contained in the set. public int Count => _count - _freeCount; + /// + /// Gets the total numbers of elements the internal data structure can hold without resizing. + /// + public int Capacity => _entries?.Length ?? 0; + bool ICollection.IsReadOnly => false; #endregion @@ -1005,10 +1010,24 @@ private void Resize(int newSize, bool forceNewHashCodes) /// Sets the capacity of a object to the actual number of elements it contains, /// rounded up to a nearby, implementation-specific value. /// - public void TrimExcess() + public void TrimExcess() => SetCapacity(Count); + + /// + /// Sets the capacity of a object to the specified number of entries, + /// rounded up to a nearby, implementation-specific value. + /// + /// The new capacity. + /// Passed capacity is lower than entries count. + public void TrimExcess(int capacity) { - int capacity = Count; + ArgumentOutOfRangeException.ThrowIfLessThan(capacity, Count); + SetCapacity(capacity); + } + + private void SetCapacity(int capacity) + { + Debug.Assert(capacity >= Count); int newSize = HashHelpers.GetPrime(capacity); Entry[]? oldEntries = _entries; int currentCapacity = oldEntries == null ? 0 : oldEntries.Length; diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Queue.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Queue.cs index 9d9ec66416736d..58afecf920c442 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Queue.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Queue.cs @@ -52,15 +52,15 @@ public Queue(IEnumerable collection) if (_size != _array.Length) _tail = _size; } - public int Count - { - get { return _size; } - } + public int Count => _size; - bool ICollection.IsSynchronized - { - get { return false; } - } + /// + /// Gets the total numbers of elements the internal data structure can hold without resizing. + /// + public int Capacity => _array.Length; + + /// + bool ICollection.IsSynchronized => false; object ICollection.SyncRoot => this; @@ -309,6 +309,7 @@ public T[] ToArray() // must be >= _size. private void SetCapacity(int capacity) { + Debug.Assert(capacity >= _size); T[] newarray = new T[capacity]; if (_size > 0) { @@ -358,6 +359,22 @@ public void TrimExcess() } } + /// + /// Sets the capacity of a object to the specified number of entries. + /// + /// The new capacity. + /// Passed capacity is lower than entries count. + public void TrimExcess(int capacity) + { + ArgumentOutOfRangeException.ThrowIfNegative(capacity); + ArgumentOutOfRangeException.ThrowIfLessThan(capacity, _size); + + if (capacity == _array.Length) + return; + + SetCapacity(capacity); + } + /// /// Ensures that the capacity of this Queue is at least the specified . ///