Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

issue-44801 EnsureCapacity Apis For List Stack Queue #47149

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
4001c7b
issue-44801 Initial commit: public List.EnsureCapacity.
lateapexearlyspeed Jan 19, 2021
e49f04f
Create Stack.EnsureCapacity().
lateapexearlyspeed Jan 20, 2021
78634df
issue-44801 Fix comment for Stack.
lateapexearlyspeed Jan 22, 2021
fc77d7e
issue-44801 Fix comment: Move temp MaxArrayLength into near usage sco…
lateapexearlyspeed Jan 23, 2021
b48d082
issue-44801 Fix comment for List.
lateapexearlyspeed Jan 25, 2021
5642f46
issue-44801 Create EnsureCapacity for Queue.
lateapexearlyspeed Jan 26, 2021
21595ac
issue-44801 Create tests for List.EnsureCapacity.
lateapexearlyspeed Jan 29, 2021
a2435c3
issue-44801 Create tests for Stack.EnsureCapacity.
lateapexearlyspeed Feb 1, 2021
d295d74
issue-44801 Create tests for Queue.EnsureCapacity.
lateapexearlyspeed Feb 2, 2021
0a91377
issue-44801 Update version if updating internal buffer.
lateapexearlyspeed Feb 2, 2021
44a06b7
issue-44801 Update test cases to verify invalidating enumeration (Lis…
lateapexearlyspeed Feb 4, 2021
46b7b59
issue-44801 Improve code change for List:
lateapexearlyspeed Feb 10, 2021
f9d6c25
issue-44801 Fix comments: consider integer overflow; refactor methods.
lateapexearlyspeed Feb 18, 2021
05aca89
Update src/libraries/System.Private.CoreLib/src/System/Collections/Ge…
lateapexearlyspeed Feb 18, 2021
253bcdc
ensure same algorithm is used for all resize operations
eiriktsarpalis Feb 18, 2021
4036445
revert array length check in List.EnsureCapacityCore
eiriktsarpalis Feb 18, 2021
2b58b4c
remove GrowFactor constant
eiriktsarpalis Feb 19, 2021
cae1dca
skip large capacity tests on mono.
eiriktsarpalis Feb 19, 2021
b06514d
Fix overflow handling when capacity < MaxArrayLength; add clarifying …
eiriktsarpalis Feb 19, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/libraries/System.Collections/ref/System.Collections.cs
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ public void Clear() { }
public void CopyTo(int index, T[] array, int arrayIndex, int count) { }
public void CopyTo(T[] array) { }
public void CopyTo(T[] array, int arrayIndex) { }
public int EnsureCapacity(int capacity) { throw null; }
public bool Exists(System.Predicate<T> match) { throw null; }
public T? Find(System.Predicate<T> match) { throw null; }
public System.Collections.Generic.List<T> FindAll(System.Predicate<T> match) { throw null; }
Expand Down Expand Up @@ -443,6 +444,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 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; }
public partial struct Enumerator : System.Collections.Generic.IEnumerator<T>, System.Collections.IEnumerator, System.IDisposable
Expand Down Expand Up @@ -699,6 +701,7 @@ public void Push(T item) { }
System.Collections.Generic.IEnumerator<T> System.Collections.Generic.IEnumerable<T>.GetEnumerator() { throw null; }
void System.Collections.ICollection.CopyTo(System.Array array, int arrayIndex) { }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; }
public int EnsureCapacity(int capacity) { throw null; }
public T[] ToArray() { throw null; }
public void TrimExcess() { }
public bool TryPeek([System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out T result) { throw null; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ public class Queue<T> : IEnumerable<T>,
private int _size; // Number of elements.
private int _version;

private const int MinimumGrow = 4;
private const int GrowFactor = 200; // double each time

// Creates a queue with room for capacity objects. The default initial
// capacity and grow factor are used.
public Queue()
Expand Down Expand Up @@ -183,12 +180,7 @@ public void Enqueue(T item)
{
if (_size == _array.Length)
{
int newcapacity = (int)(_array.Length * (long)GrowFactor / 100);
if (newcapacity < _array.Length + MinimumGrow)
{
newcapacity = _array.Length + MinimumGrow;
}
SetCapacity(newcapacity);
EnsureCapacityCore(_size + 1);
}

_array[_tail] = item;
Expand Down Expand Up @@ -385,6 +377,53 @@ public void TrimExcess()
}
}

/// <summary>
/// Ensures that the capacity of this Queue is at least the specified <paramref name="capacity"/>.
/// </summary>
/// <param name="capacity">The minimum capacity to ensure.</param>
public int EnsureCapacity(int capacity)
{
if (capacity < 0)
{
throw new ArgumentOutOfRangeException(nameof(capacity), capacity, SR.ArgumentOutOfRange_NeedNonNegNum);
}

if (_array.Length < capacity)
{
EnsureCapacityCore(capacity);
}

return _array.Length;
}

private void EnsureCapacityCore(int capacity)
{
Debug.Assert(capacity >= 0);

if (_array.Length < capacity)
{
// Array.MaxArrayLength is internal to S.P.CoreLib, replicate here.
const int MaxArrayLength = 0X7FEFFFFF;
const int GrowFactor = 2;
const int MinimumGrow = 4;

int newcapacity = GrowFactor * _array.Length;

// Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow.
// Note that this check works even when _items.Length overflowed thanks to the (uint) cast
if ((uint)newcapacity > MaxArrayLength) newcapacity = MaxArrayLength;

// Ensure minimum growth is respected.
newcapacity = Math.Max(newcapacity, _array.Length + MinimumGrow);

// If the computed capacity is still less than specified, set to the original argument.
// Capacities exceeding MaxArrayLength will be surfaced as OutOfMemoryException by Array.Resize.
if (newcapacity < capacity) newcapacity = capacity;

SetCapacity(newcapacity);
}
}

// Implements an enumerator for a Queue. The enumerator uses the
// internal version number of the list to ensure that no modifications are
// made to the list while an enumeration is in progress.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,12 +282,58 @@ public void Push(T item)
[MethodImpl(MethodImplOptions.NoInlining)]
private void PushWithResize(T item)
{
Array.Resize(ref _array, (_array.Length == 0) ? DefaultCapacity : 2 * _array.Length);
Debug.Assert(_size == _array.Length);
EnsureCapacityCore(_size + 1);
_array[_size] = item;
_version++;
_size++;
}

/// <summary>
/// Ensures that the capacity of this Stack is at least the specified <paramref name="capacity"/>.
/// If the current capacity of the Stack is less than specified <paramref name="capacity"/>,
/// the capacity is increased by continuously twice current capacity until it is at least the specified <paramref name="capacity"/>.
/// </summary>
/// <param name="capacity">The minimum capacity to ensure.</param>
public int EnsureCapacity(int capacity)
{
if (capacity < 0)
{
throw new ArgumentOutOfRangeException(nameof(capacity), capacity, SR.ArgumentOutOfRange_NeedNonNegNum);
}

if (_array.Length < capacity)
{
EnsureCapacityCore(capacity);
_version++;
}

return _array.Length;
}

private void EnsureCapacityCore(int capacity)
{
Debug.Assert(capacity >= 0);

if (_array.Length < capacity)
{
// Array.MaxArrayLength is internal to S.P.CoreLib, replicate here.
const int MaxArrayLength = 0X7FEFFFFF;

int newcapacity = _array.Length == 0 ? DefaultCapacity : 2 * _array.Length;

// Allow the list to grow to maximum possible capacity (~2G elements) before encountering overflow.
// Note that this check works even when _items.Length overflowed thanks to the (uint) cast.
if ((uint)newcapacity > MaxArrayLength) newcapacity = MaxArrayLength;

// If computed capacity is still less than specified, set to the original argument.
// Capacities exceeding MaxArrayLength will be surfaced as OutOfMemoryException by Array.Resize.
if (newcapacity < capacity) newcapacity = capacity;

Array.Resize(ref _array, newcapacity);
}
}

// Copies the Stack to an array, in the same order Pop would return the items.
public T[] ToArray()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using Xunit;

namespace System.Collections.Tests
{
/// <summary>
/// Contains tests that ensure the correctness of the List class.
/// </summary>
public abstract partial class List_Generic_Tests<T> : IList_Generic_Tests<T>
{
[Theory]
[MemberData(nameof(ValidCollectionSizes))]
public void EnsureCapacity_RequestingLargerCapacity_DoesInvalidateEnumeration(int count)
{
List<T> list = GenericListFactory(count);
IEnumerator<T> copiedListEnumerator = new List<T>(list).GetEnumerator();
IEnumerator<T> enumerator = list.GetEnumerator();
var capacity = list.Capacity;

list.EnsureCapacity(capacity + 1);

Assert.Throws<InvalidOperationException>(() => enumerator.MoveNext());
}

[Fact]
public void EnsureCapacity_NotInitialized_RequestedZero_ReturnsZero()
{
var list = new List<T>();
Assert.Equal(0, list.EnsureCapacity(0));
Assert.Equal(0, list.Capacity);
}

[Fact]
public void EnsureCapacity_NegativeCapacityRequested_Throws()
{
var list = new List<T>();
AssertExtensions.Throws<ArgumentOutOfRangeException>("capacity", () => list.EnsureCapacity(-1));
}

const int MaxArraySize = 0X7FEFFFFF;

[Theory]
[InlineData(5, MaxArraySize + 1)]
[InlineData(1, int.MaxValue)]
[SkipOnMono("mono forces no restrictions on array size.")]
public void EnsureCapacity_LargeCapacity_Throws(int count, int requestCapacity)
{
List<T> list = GenericListFactory(count);
Assert.Throws<OutOfMemoryException>(() => list.EnsureCapacity(requestCapacity));
}

[Theory]
[InlineData(5)]
public void EnsureCapacity_RequestedCapacitySmallerThanOrEqualToCurrent_CapacityUnchanged(int currentCapacity)
{
var list = new List<T>(currentCapacity);

for (int requestCapacity = 0; requestCapacity <= currentCapacity; requestCapacity++)
{
Assert.Equal(currentCapacity, list.EnsureCapacity(requestCapacity));
Assert.Equal(currentCapacity, list.Capacity);
}
}

[Theory]
[MemberData(nameof(ValidCollectionSizes))]
public void EnsureCapacity_RequestedCapacitySmallerThanOrEqualToCount_CapacityUnchanged(int count)
{
List<T> list = GenericListFactory(count);
var currentCapacity = list.Capacity;

for (int requestCapacity = 0; requestCapacity <= count; requestCapacity++)
{
Assert.Equal(currentCapacity, list.EnsureCapacity(requestCapacity));
Assert.Equal(currentCapacity, list.Capacity);
}
}

[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(5)]
public void EnsureCapacity_CapacityIsAtLeastTheRequested(int count)
{
List<T> list = GenericListFactory(count);

int currentCapacity = list.Capacity;
int requestCapacity = currentCapacity + 1;
int newCapacity = list.EnsureCapacity(requestCapacity);
Assert.InRange(newCapacity, requestCapacity, int.MaxValue);
}

[Theory]
[MemberData(nameof(ValidCollectionSizes))]
public void EnsureCapacity_RequestingLargerCapacity_DoesNotImpactListContent(int count)
{
List<T> list = GenericListFactory(count);
var copiedList = new List<T>(list);

list.EnsureCapacity(list.Capacity + 1);
Assert.Equal(copiedList, list);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -320,5 +320,97 @@ public void Queue_Generic_TryPeek_EmptyQueue_ReturnsFalse()
Assert.False(new Queue<T>().TryPeek(out result));
Assert.Equal(default(T), result);
}

[Theory]
[MemberData(nameof(ValidCollectionSizes))]
public void Queue_Generic_EnsureCapacity_RequestingLargerCapacity_DoesInvalidateEnumeration(int count)
{
Queue<T> queue = GenericQueueFactory(count);
IEnumerator<T> copiedEnumerator = new List<T>(queue).GetEnumerator();
IEnumerator<T> enumerator = queue.GetEnumerator();

queue.EnsureCapacity(count + 1);

Assert.Throws<InvalidOperationException>(() => enumerator.MoveNext());
}

[Fact]
public void Queue_Generic_EnsureCapacity_NotInitialized_RequestedZero_ReturnsZero()
{
var queue = GenericQueueFactory();
Assert.Equal(0, queue.EnsureCapacity(0));
}

[Fact]
public void Queue_Generic_EnsureCapacity_NegativeCapacityRequested_Throws()
{
var queue = GenericQueueFactory();
AssertExtensions.Throws<ArgumentOutOfRangeException>("capacity", () => queue.EnsureCapacity(-1));
}

const int MaxArraySize = 0X7FEFFFFF;

[Theory]
[InlineData(MaxArraySize + 1)]
[InlineData(int.MaxValue)]
[SkipOnMono("mono forces no restrictions on array size.")]
public void Queue_Generic_EnsureCapacity_LargeCapacityRequested_Throws(int requestedCapacity)
{
var queue = GenericQueueFactory();
AssertExtensions.Throws<OutOfMemoryException>(() => queue.EnsureCapacity(requestedCapacity));
}

[Theory]
[InlineData(5)]
public void Queue_Generic_EnsureCapacity_RequestedCapacitySmallerThanOrEqualToCurrent_CapacityUnchanged(int currentCapacity)
{
var queue = new Queue<T>(currentCapacity);

for (int requestCapacity = 0; requestCapacity <= currentCapacity; requestCapacity++)
{
Assert.Equal(currentCapacity, queue.EnsureCapacity(requestCapacity));
}
}

[Theory]
[MemberData(nameof(ValidCollectionSizes))]
public void Queue_Generic_EnsureCapacity_RequestedCapacitySmallerThanOrEqualToCount_CapacityUnchanged(int count)
{
Queue<T> queue = GenericQueueFactory(count);

for (int requestCapacity = 0; requestCapacity <= count; requestCapacity++)
{
Assert.Equal(count, queue.EnsureCapacity(requestCapacity));
}
}

[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(5)]
public void Queue_Generic_EnsureCapacity_CapacityIsAtLeastTheRequested(int count)
{
Queue<T> queue = GenericQueueFactory(count);

int requestCapacity = count + 1;
int newCapacity = queue.EnsureCapacity(requestCapacity);
Assert.InRange(newCapacity, requestCapacity, int.MaxValue);
}

[Theory]
[MemberData(nameof(ValidCollectionSizes))]
public void Queue_Generic_EnsureCapacity_RequestingLargerCapacity_DoesNotImpactQueueContent(int count)
{
Queue<T> queue = GenericQueueFactory(count);
var copiedList = new List<T>(queue);

queue.EnsureCapacity(count + 1);
Assert.Equal(copiedList, queue);

for (int i = 0; i < count; i++)
{
Assert.Equal(copiedList[i], queue.Dequeue());
}
}
}
}
Loading