未验证 提交 28be2573 编写于 作者: L LateApexEarlySpeed 提交者: GitHub

issue-44801 EnsureCapacity Apis For List Stack Queue (#47149)

* issue-44801 Initial commit: public List.EnsureCapacity.

* Create Stack.EnsureCapacity().

* issue-44801 Fix comment for Stack.

* issue-44801 Fix comment: Move temp MaxArrayLength into near usage scope for Stack.

* issue-44801 Fix comment for List.

* issue-44801 Create EnsureCapacity for Queue.

* issue-44801 Create tests for List.EnsureCapacity.

* issue-44801 Create tests for Stack.EnsureCapacity.

* issue-44801 Create tests for Queue.EnsureCapacity.

* issue-44801 Update version if updating internal buffer.

* issue-44801 Update test cases to verify invalidating enumeration (List, Stack, Queue).

* issue-44801 Improve code change for List:

1. Avoid incrementing version number twice in one Insert (or Add) method call;
2. Avoid more capacity check for Insert (or Add) method.

* issue-44801 Fix comments: consider integer overflow; refactor methods.

* Update src/libraries/System.Private.CoreLib/src/System/Collections/Generic/List.cs

Fix comment: update xml doc.
Co-authored-by: NEirik Tsarpalis <eirik.tsarpalis@gmail.com>

* ensure same algorithm is used for all resize operations

* revert array length check in List.EnsureCapacityCore

* remove GrowFactor constant

* skip large capacity tests on mono.

* Fix overflow handling when capacity < MaxArrayLength; add clarifying comments
Co-authored-by: NEirik Tsarpalis <eirik.tsarpalis@gmail.com>
上级 0db5b466
......@@ -324,6 +324,7 @@ public partial class List<T> : System.Collections.Generic.ICollection<T>, System
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; }
......@@ -443,6 +444,7 @@ public partial class Queue<T> : System.Collections.Generic.IEnumerable<T>, Syste
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
......@@ -699,6 +701,7 @@ public partial class Stack<T> : System.Collections.Generic.IEnumerable<T>, Syste
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; }
......
......@@ -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()
......@@ -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;
......@@ -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.
......
......@@ -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()
{
......
// 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);
}
}
}
......@@ -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());
}
}
}
}
......@@ -286,5 +286,97 @@ public void Stack_Generic_TryPeek_EmptyStack_ReturnsFalse()
Assert.False(new Stack<T>().TryPeek(out result));
Assert.Equal(default(T), result);
}
[Theory]
[MemberData(nameof(ValidCollectionSizes))]
public void Stack_Generic_EnsureCapacity_RequestingLargerCapacity_DoesInvalidateEnumeration(int count)
{
Stack<T> stack = GenericStackFactory(count);
IEnumerator<T> copiedEnumerator = new List<T>(stack).GetEnumerator();
IEnumerator<T> enumerator = stack.GetEnumerator();
stack.EnsureCapacity(count + 1);
Assert.Throws<InvalidOperationException>(() => enumerator.MoveNext());
}
[Fact]
public void Stack_Generic_EnsureCapacity_NotInitialized_RequestedZero_ReturnsZero()
{
var stack = GenericStackFactory();
Assert.Equal(0, stack.EnsureCapacity(0));
}
[Fact]
public void Stack_Generic_EnsureCapacity_NegativeCapacityRequested_Throws()
{
var stack = GenericStackFactory();
AssertExtensions.Throws<ArgumentOutOfRangeException>("capacity", () => stack.EnsureCapacity(-1));
}
const int MaxArraySize = 0X7FEFFFFF;
[Theory]
[InlineData(MaxArraySize + 1)]
[InlineData(int.MaxValue)]
[SkipOnMono("mono forces no restrictions on array size.")]
public void Stack_Generic_EnsureCapacity_LargeCapacityRequested_Throws(int requestedCapacity)
{
var stack = GenericStackFactory();
AssertExtensions.Throws<OutOfMemoryException>(() => stack.EnsureCapacity(requestedCapacity));
}
[Theory]
[InlineData(5)]
public void Stack_Generic_EnsureCapacity_RequestedCapacitySmallerThanOrEqualToCurrent_CapacityUnchanged(int currentCapacity)
{
var stack = new Stack<T>(currentCapacity);
for (int requestCapacity = 0; requestCapacity <= currentCapacity; requestCapacity++)
{
Assert.Equal(currentCapacity, stack.EnsureCapacity(requestCapacity));
}
}
[Theory]
[MemberData(nameof(ValidCollectionSizes))]
public void Stack_Generic_EnsureCapacity_RequestedCapacitySmallerThanOrEqualToCount_CapacityUnchanged(int count)
{
Stack<T> stack = GenericStackFactory(count);
for (int requestCapacity = 0; requestCapacity <= count; requestCapacity++)
{
Assert.Equal(count, stack.EnsureCapacity(requestCapacity));
}
}
[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(5)]
public void Stack_Generic_EnsureCapacity_CapacityIsAtLeastTheRequested(int count)
{
Stack<T> stack = GenericStackFactory(count);
int requestCapacity = count + 1;
int newCapacity = stack.EnsureCapacity(requestCapacity);
Assert.InRange(newCapacity, requestCapacity, int.MaxValue);
}
[Theory]
[MemberData(nameof(ValidCollectionSizes))]
public void Stack_Generic_EnsureCapacity_RequestingLargerCapacity_DoesNotImpactStackContent(int count)
{
Stack<T> stack = GenericStackFactory(count);
var copiedList = new List<T>(stack);
stack.EnsureCapacity(count + 1);
Assert.Equal(copiedList, stack);
for (int i = 0; i < count; i++)
{
Assert.Equal(copiedList[i], stack.Pop());
}
}
}
}
......@@ -93,6 +93,7 @@
<Compile Include="Generic\List\List.Generic.Tests.Constructor.cs" />
<Compile Include="Generic\List\List.Generic.Tests.ConvertAll.cs" />
<Compile Include="Generic\List\List.Generic.Tests.Find.cs" />
<Compile Include="Generic\List\List.Generic.Tests.EnsureCapacity.cs" />
<Compile Include="Generic\List\List.Generic.Tests.Remove.cs" />
<Compile Include="Generic\List\List.Generic.Tests.Sort.cs" />
<Compile Include="Generic\PriorityQueue\PriorityQueue.Generic.cs" />
......
......@@ -214,7 +214,7 @@ public void Add(T item)
private void AddWithResize(T item)
{
int size = _size;
EnsureCapacity(size + 1);
EnsureCapacityCore(size + 1);
_size = size + 1;
_items[size] = item;
}
......@@ -391,21 +391,48 @@ public void CopyTo(T[] array, int arrayIndex)
Array.Copy(_items, 0, array, arrayIndex, _size);
}
// Ensures that the capacity of this list is at least the given minimum
// value. If the current capacity of the list is less than min, the
// capacity is increased to twice the current capacity or to min,
// whichever is larger.
//
private void EnsureCapacity(int min)
/// <summary>
/// Ensures that the capacity of this list is at least the specified <paramref name="capacity"/>.
/// If the current capacity of the list 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)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
}
if (_items.Length < capacity)
{
EnsureCapacityCore(capacity);
_version++;
}
return _items.Length;
}
/// <summary>
/// Increase the capacity of this list to at least the specified <paramref name="capacity"/> by continuously twice current capacity.
/// </summary>
/// <param name="capacity">The minimum capacity to ensure.</param>
private void EnsureCapacityCore(int capacity)
{
if (_items.Length < min)
Debug.Assert(capacity >= 0);
if (_items.Length < capacity)
{
int newCapacity = _items.Length == 0 ? DefaultCapacity : _items.Length * 2;
int newcapacity = _items.Length == 0 ? DefaultCapacity : 2 * _items.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 > Array.MaxArrayLength) newCapacity = Array.MaxArrayLength;
if (newCapacity < min) newCapacity = min;
Capacity = newCapacity;
if ((uint)newcapacity > Array.MaxArrayLength) newcapacity = Array.MaxArrayLength;
// 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;
Capacity = newcapacity;
}
}
......@@ -668,7 +695,7 @@ public void Insert(int index, T item)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index, ExceptionResource.ArgumentOutOfRange_ListInsert);
}
if (_size == _items.Length) EnsureCapacity(_size + 1);
if (_size == _items.Length) EnsureCapacityCore(_size + 1);
if (index < _size)
{
Array.Copy(_items, index, _items, index + 1, _size - index);
......@@ -714,7 +741,7 @@ public void InsertRange(int index, IEnumerable<T> collection)
int count = c.Count;
if (count > 0)
{
EnsureCapacity(_size + count);
EnsureCapacityCore(_size + count);
if (index < _size)
{
Array.Copy(_items, index, _items, index + count, _size - index);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册