From f0c9ca86928316c889b9064b03b827f1fcf0f9a0 Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Thu, 4 Nov 2021 15:40:53 +0800 Subject: [PATCH 01/29] issue-22928 Initial commit: AddRange, InsertRange, ToImmutableArray, Create. --- .../ref/System.Collections.Immutable.cs | 8 ++ .../Collections/Immutable/ImmutableArray.cs | 24 ++++++ .../Collections/Immutable/ImmutableArray_1.cs | 78 +++++++++++++++---- 3 files changed, 94 insertions(+), 16 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs b/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs index b70fcca6b235c..f565f6b7a0938 100644 --- a/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs +++ b/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs @@ -90,8 +90,12 @@ public static partial class ImmutableArray public static System.Collections.Immutable.ImmutableArray Create(T item1, T item2, T item3, T item4) { throw null; } public static System.Collections.Immutable.ImmutableArray Create(params T[]? items) { throw null; } public static System.Collections.Immutable.ImmutableArray Create(T[] items, int start, int length) { throw null; } + public static System.Collections.Immutable.ImmutableArray Create(System.ReadOnlySpan items) { throw null; } + public static System.Collections.Immutable.ImmutableArray Create(System.Span items) { throw null; } public static System.Collections.Immutable.ImmutableArray ToImmutableArray(this System.Collections.Generic.IEnumerable items) { throw null; } public static System.Collections.Immutable.ImmutableArray ToImmutableArray(this System.Collections.Immutable.ImmutableArray.Builder builder) { throw null; } + public static System.Collections.Immutable.ImmutableArray ToImmutableArray(this System.ReadOnlySpan items) { throw null; } + public static System.Collections.Immutable.ImmutableArray ToImmutableArray(this System.Span items) { throw null; } } public readonly partial struct ImmutableArray : System.Collections.Generic.ICollection, System.Collections.Generic.IEnumerable, System.Collections.Generic.IList, System.Collections.Generic.IReadOnlyCollection, System.Collections.Generic.IReadOnlyList, System.Collections.ICollection, System.Collections.IEnumerable, System.Collections.IList, System.Collections.Immutable.IImmutableList, System.Collections.IStructuralComparable, System.Collections.IStructuralEquatable, System.IEquatable> { @@ -118,6 +122,8 @@ public static partial class ImmutableArray public System.Collections.Immutable.ImmutableArray Add(T item) { throw null; } public System.Collections.Immutable.ImmutableArray AddRange(System.Collections.Generic.IEnumerable items) { throw null; } public System.Collections.Immutable.ImmutableArray AddRange(System.Collections.Immutable.ImmutableArray items) { throw null; } + public System.Collections.Immutable.ImmutableArray AddRange(System.ReadOnlySpan items) { throw null; } + public System.Collections.Immutable.ImmutableArray AddRange(params T[] items) { throw null; } public System.ReadOnlyMemory AsMemory() { throw null; } public System.ReadOnlySpan AsSpan() { throw null; } public System.Collections.Immutable.ImmutableArray< @@ -152,6 +158,8 @@ public void CopyTo(T[] destination, int destinationIndex) { } public System.Collections.Immutable.ImmutableArray Insert(int index, T item) { throw null; } public System.Collections.Immutable.ImmutableArray InsertRange(int index, System.Collections.Generic.IEnumerable items) { throw null; } public System.Collections.Immutable.ImmutableArray InsertRange(int index, System.Collections.Immutable.ImmutableArray items) { throw null; } + public System.Collections.Immutable.ImmutableArray InsertRange(int index, T[] items) { throw null; } + public System.Collections.Immutable.ImmutableArray InsertRange(int index, System.ReadOnlySpan items) { throw null; } public ref readonly T ItemRef(int index) { throw null; } public int LastIndexOf(T item) { throw null; } public int LastIndexOf(T item, int startIndex) { throw null; } diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray.cs index 5558be5bfdbde..4f776d1f56740 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray.cs @@ -81,6 +81,30 @@ public static ImmutableArray Create(T item1, T item2, T item3, T item4) return new ImmutableArray(array); } + public static ImmutableArray Create(ReadOnlySpan items) + { + T[] array = items.ToArray(); + return new ImmutableArray(array); + } + + public static ImmutableArray Create(Span items) + { + T[] array = items.ToArray(); + return new ImmutableArray(array); + } + + public static ImmutableArray ToImmutableArray(this ReadOnlySpan items) + { + T[] array = items.ToArray(); + return new ImmutableArray(array); + } + + public static ImmutableArray ToImmutableArray(this Span items) + { + T[] array = items.ToArray(); + return new ImmutableArray(array); + } + /// /// Creates an populated with the contents of the specified sequence. /// diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs index e8661bd05e509..e422c0d67d2c1 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs @@ -365,44 +365,49 @@ public ImmutableArray InsertRange(int index, IEnumerable items) return new ImmutableArray(tmp); } - /// - /// Inserts the specified values at the specified index. - /// - /// The index at which to insert the value. - /// The elements to insert. - /// The new immutable collection. - public ImmutableArray InsertRange(int index, ImmutableArray items) + public ImmutableArray InsertRange(int index, T[] items) { var self = this; self.ThrowNullRefIfNotInitialized(); - items.ThrowNullRefIfNotInitialized(); Requires.Range(index >= 0 && index <= self.Length, nameof(index)); + Requires.NotNull(items, nameof(items)); - if (self.IsEmpty) + if (items.Length == 0) { - return items; + return self; } - else if (items.IsEmpty) + if (self.Length == 0) { - return self; + return new ImmutableArray(items); } - T[] tmp = new T[self.Length + items.Length]; - + var tmp = new T[self.Length + items.Length]; if (index != 0) { Array.Copy(self.array!, tmp, index); } + Array.Copy(items, 0, tmp, index, items.Length); if (index != self.Length) { Array.Copy(self.array!, index, tmp, index + items.Length, self.Length - index); } - Array.Copy(items.array!, 0, tmp, index, items.Length); - return new ImmutableArray(tmp); } + /// + /// Inserts the specified values at the specified index. + /// + /// The index at which to insert the value. + /// The elements to insert. + /// The new immutable collection. + public ImmutableArray InsertRange(int index, ImmutableArray items) + { + var self = this; + items.ThrowNullRefIfNotInitialized(); + return self.InsertRange(index, items.array!); + } + /// /// Returns a new array with the specified value inserted at the end. /// @@ -441,6 +446,47 @@ public ImmutableArray AddRange(ImmutableArray items) return self.InsertRange(self.Length, items); } + public ImmutableArray InsertRange(int index, ReadOnlySpan items) + { + var self = this; + self.ThrowNullRefIfNotInitialized(); + Requires.Range(index >= 0 && index <= self.Length, nameof(index)); + + if (items.IsEmpty) + { + return self; + } + if (self.Length == 0) + { + return items.ToImmutableArray(); + } + + var tmp = new T[self.Length + items.Length]; + if (index != 0) + { + Array.Copy(self.array!, tmp, index); + } + items.CopyTo(new Span(tmp, index, items.Length)); + if (index != self.Length) + { + Array.Copy(self.array!, index, tmp, index + items.Length, self.Length - index); + } + + return new ImmutableArray(tmp); + } + + public ImmutableArray AddRange(ReadOnlySpan items) + { + var self = this; + return self.InsertRange(self.Length, items); + } + + public ImmutableArray AddRange(params T[] items) + { + var self = this; + return self.InsertRange(self.Length, items); + } + /// /// Returns an array with the item at the specified position replaced. /// From 4ce2f253988e882b48d5ef23cd8e629f1c192561 Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Fri, 12 Nov 2021 16:20:08 +0800 Subject: [PATCH 02/29] issue-22928 CopyTo, RemoveRange, Slice. Skip old tests. --- .../ref/System.Collections.Immutable.cs | 26 +- .../Collections/Immutable/ImmutableArray.cs | 24 ++ .../Immutable/ImmutableArray_1.Builder.cs | 115 ++------- .../Immutable/ImmutableArray_1.Minimal.cs | 2 +- .../Collections/Immutable/ImmutableArray_1.cs | 231 ++++++++++++------ .../tests/ImmutableArrayBuilderTest.cs | 38 +-- .../tests/ImmutableArrayTest.cs | 64 +++-- 7 files changed, 274 insertions(+), 226 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs b/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs index f565f6b7a0938..1fd9c3dc97aa8 100644 --- a/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs +++ b/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs @@ -120,12 +120,12 @@ public static partial class ImmutableArray bool System.Collections.IList.IsReadOnly { get { throw null; } } object? System.Collections.IList.this[int index] { get { throw null; } set { } } public System.Collections.Immutable.ImmutableArray Add(T item) { throw null; } - public System.Collections.Immutable.ImmutableArray AddRange(System.Collections.Generic.IEnumerable items) { throw null; } - public System.Collections.Immutable.ImmutableArray AddRange(System.Collections.Immutable.ImmutableArray items) { throw null; } public System.Collections.Immutable.ImmutableArray AddRange(System.ReadOnlySpan items) { throw null; } public System.Collections.Immutable.ImmutableArray AddRange(params T[] items) { throw null; } public System.ReadOnlyMemory AsMemory() { throw null; } public System.ReadOnlySpan AsSpan() { throw null; } + public System.ReadOnlySpan AsSpan(int start, int length) { throw null; } + // public System.ReadOnlySpan AsSpan(System.Range range) { throw null; } public System.Collections.Immutable.ImmutableArray< #nullable disable TOther @@ -146,6 +146,7 @@ public static System.Collections.Immutable.ImmutableArray< public void CopyTo(int sourceIndex, T[] destination, int destinationIndex, int length) { } public void CopyTo(T[] destination) { } public void CopyTo(T[] destination, int destinationIndex) { } + public void CopyTo(System.Span destination) { } public bool Equals(System.Collections.Immutable.ImmutableArray other) { throw null; } public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } public System.Collections.Immutable.ImmutableArray.Enumerator GetEnumerator() { throw null; } @@ -156,8 +157,6 @@ public void CopyTo(T[] destination, int destinationIndex) { } public int IndexOf(T item, int startIndex, int count) { throw null; } public int IndexOf(T item, int startIndex, int count, System.Collections.Generic.IEqualityComparer? equalityComparer) { throw null; } public System.Collections.Immutable.ImmutableArray Insert(int index, T item) { throw null; } - public System.Collections.Immutable.ImmutableArray InsertRange(int index, System.Collections.Generic.IEnumerable items) { throw null; } - public System.Collections.Immutable.ImmutableArray InsertRange(int index, System.Collections.Immutable.ImmutableArray items) { throw null; } public System.Collections.Immutable.ImmutableArray InsertRange(int index, T[] items) { throw null; } public System.Collections.Immutable.ImmutableArray InsertRange(int index, System.ReadOnlySpan items) { throw null; } public ref readonly T ItemRef(int index) { throw null; } @@ -174,11 +173,9 @@ public void CopyTo(T[] destination, int destinationIndex) { } public System.Collections.Immutable.ImmutableArray Remove(T item, System.Collections.Generic.IEqualityComparer? equalityComparer) { throw null; } public System.Collections.Immutable.ImmutableArray RemoveAll(System.Predicate match) { throw null; } public System.Collections.Immutable.ImmutableArray RemoveAt(int index) { throw null; } - public System.Collections.Immutable.ImmutableArray RemoveRange(System.Collections.Generic.IEnumerable items) { throw null; } - public System.Collections.Immutable.ImmutableArray RemoveRange(System.Collections.Generic.IEnumerable items, System.Collections.Generic.IEqualityComparer? equalityComparer) { throw null; } - public System.Collections.Immutable.ImmutableArray RemoveRange(System.Collections.Immutable.ImmutableArray items) { throw null; } - public System.Collections.Immutable.ImmutableArray RemoveRange(System.Collections.Immutable.ImmutableArray items, System.Collections.Generic.IEqualityComparer? equalityComparer) { throw null; } - public System.Collections.Immutable.ImmutableArray RemoveRange(int index, int length) { throw null; } + public System.Collections.Immutable.ImmutableArray RemoveRange(System.ReadOnlySpan items, System.Collections.Generic.IEqualityComparer? equalityComparer = null) { throw null; } + public System.Collections.Immutable.ImmutableArray RemoveRange(T[] items, System.Collections.Generic.IEqualityComparer? equalityComparer = null) { throw null; } + public System.Collections.Immutable.ImmutableArray Slice(int start, int length) { throw null; } public System.Collections.Immutable.ImmutableArray Replace(T oldValue, T newValue) { throw null; } public System.Collections.Immutable.ImmutableArray Replace(T oldValue, T newValue, System.Collections.Generic.IEqualityComparer? equalityComparer) { throw null; } public System.Collections.Immutable.ImmutableArray SetItem(int index, T item) { throw null; } @@ -225,18 +222,13 @@ internal Builder() { } public T this[int index] { get { throw null; } set { } } bool System.Collections.Generic.ICollection.IsReadOnly { get { throw null; } } public void Add(T item) { } - public void AddRange(System.Collections.Generic.IEnumerable items) { } - public void AddRange(System.Collections.Immutable.ImmutableArray items) { } - public void AddRange(System.Collections.Immutable.ImmutableArray items, int length) { } public void AddRange(System.Collections.Immutable.ImmutableArray.Builder items) { } - public void AddRange(params T[] items) { } - public void AddRange(T[] items, int length) { } - public void AddRange(System.Collections.Immutable.ImmutableArray items) where TDerived : T { } - public void AddRange(System.Collections.Immutable.ImmutableArray.Builder items) where TDerived : T { } - public void AddRange(TDerived[] items) where TDerived : T { } + public void AddRange(System.ReadOnlySpan items) { } + public void AddRange(System.ReadOnlySpan items) where TDerived : T { } public void Clear() { } public bool Contains(T item) { throw null; } public void CopyTo(T[] array, int index) { } + public void CopyTo(System.Span destination) { } public System.Collections.Generic.IEnumerator GetEnumerator() { throw null; } public int IndexOf(T item) { throw null; } public int IndexOf(T item, int startIndex) { throw null; } diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray.cs index 4f776d1f56740..b59478ce6dcab 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray.cs @@ -81,24 +81,48 @@ public static ImmutableArray Create(T item1, T item2, T item3, T item4) return new ImmutableArray(array); } + /// + /// Creates an with the specified elements. + /// + /// The type of element stored in the array. + /// The elements to store in the array. + /// An immutable array. public static ImmutableArray Create(ReadOnlySpan items) { T[] array = items.ToArray(); return new ImmutableArray(array); } + /// + /// Creates an with the specified elements. + /// + /// The type of element stored in the array. + /// The elements to store in the array. + /// An immutable array. public static ImmutableArray Create(Span items) { T[] array = items.ToArray(); return new ImmutableArray(array); } + /// + /// Produce an immutable array of contents from specified elements. + /// + /// The type of element in the list. + /// The elements to store in the array. + /// An immutable array. public static ImmutableArray ToImmutableArray(this ReadOnlySpan items) { T[] array = items.ToArray(); return new ImmutableArray(array); } + /// + /// Produce an immutable array of contents from specified elements. + /// + /// The type of element in the list. + /// The elements to store in the array. + /// An immutable array. public static ImmutableArray ToImmutableArray(this Span items) { T[] array = items.ToArray(); diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs index bdbb036a4b7e7..94d2213060b8b 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs @@ -255,25 +255,14 @@ public void Add(T item) /// Adds the specified items to the end of the array. /// /// The items. - public void AddRange(IEnumerable items) + public void AddRange(ReadOnlySpan items) where TDerived : T { - Requires.NotNull(items, nameof(items)); - - int count; - if (items.TryGetCount(out count)) - { - this.EnsureCapacity(this.Count + count); - - if (items.TryCopyTo(_elements, _count)) - { - _count += count; - return; - } - } + int offset = this.Count; + this.Count += items.Length; - foreach (var item in items) + for (int i = 0; i < items.Length; i++) { - this.Add(item); + _elements[offset + i] = items[i]; } } @@ -281,80 +270,12 @@ public void AddRange(IEnumerable items) /// Adds the specified items to the end of the array. /// /// The items. - public void AddRange(params T[] items) - { - Requires.NotNull(items, nameof(items)); - - var offset = this.Count; - this.Count += items.Length; - - Array.Copy(items, 0, _elements, offset, items.Length); - } - - /// - /// Adds the specified items to the end of the array. - /// - /// The items. - public void AddRange(TDerived[] items) where TDerived : T + public void AddRange(ReadOnlySpan items) { - Requires.NotNull(items, nameof(items)); - - var offset = this.Count; + int offset = this.Count; this.Count += items.Length; - Array.Copy(items, 0, _elements, offset, items.Length); - } - - /// - /// Adds the specified items to the end of the array. - /// - /// The items. - /// The number of elements from the source array to add. - public void AddRange(T[] items, int length) - { - Requires.NotNull(items, nameof(items)); - Requires.Range(length >= 0 && length <= items.Length, nameof(length)); - - var offset = this.Count; - this.Count += length; - - Array.Copy(items, 0, _elements, offset, length); - } - - /// - /// Adds the specified items to the end of the array. - /// - /// The items. - public void AddRange(ImmutableArray items) - { - this.AddRange(items, items.Length); - } - - /// - /// Adds the specified items to the end of the array. - /// - /// The items. - /// The number of elements from the source array to add. - public void AddRange(ImmutableArray items, int length) - { - Requires.Range(length >= 0, nameof(length)); - - if (items.array != null) - { - this.AddRange(items.array, length); - } - } - - /// - /// Adds the specified items to the end of the array. - /// - /// The items. - public void AddRange(ImmutableArray items) where TDerived : T - { - if (items.array != null) - { - this.AddRange(items.array); - } + items.CopyTo(new Span(_elements, offset, items.Length)); } /// @@ -367,16 +288,6 @@ public void AddRange(Builder items) this.AddRange(items._elements, items.Count); } - /// - /// Adds the specified items to the end of the array. - /// - /// The items. - public void AddRange(ImmutableArray.Builder items) where TDerived : T - { - Requires.NotNull(items, nameof(items)); - this.AddRange(items._elements, items.Count); - } - /// /// Removes the specified element. /// @@ -449,6 +360,16 @@ public void CopyTo(T[] array, int index) Array.Copy(_elements, 0, array, index, this.Count); } + /// + /// Copies the current contents to the specified . + /// + /// The to copy to. + public void CopyTo(Span destination) + { + Requires.Range(this.Count <= destination.Length, nameof(destination)); + new ReadOnlySpan(_elements).CopyTo(destination); + } + /// /// Resizes the array to accommodate the specified capacity requirement. /// diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Minimal.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Minimal.cs index 560fd76d18adf..4bf691a853782 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Minimal.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Minimal.cs @@ -269,7 +269,7 @@ public ImmutableArray.Builder ToBuilder() } var builder = new Builder(self.Length); - builder.AddRange(self); + builder.AddRange(self.AsSpan()); return builder; } diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs index e422c0d67d2c1..c036835edb949 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs @@ -90,8 +90,31 @@ T IReadOnlyList.this[int index] } } + /// + /// Create a over current + /// + /// The representation of the public ReadOnlySpan AsSpan() => new ReadOnlySpan(array); + /// + /// Creates a over the portion of current beginning at a specified position for a specified length. + /// + /// The index at which to begin the span. + /// The number of items in the span. + /// The representation of the + public ReadOnlySpan AsSpan(int start, int length) => new ReadOnlySpan(array, start, length); + + /* + public ReadOnlySpan AsSpan(Range range) + { + var self = this; + self.ThrowNullRefIfNotInitialized(); + + (int start, int length) = range.GetOffsetAndLength(self.Length); + return new ReadOnlySpan(self.array, start, length); + } + */ + public ReadOnlyMemory AsMemory() => new ReadOnlyMemory(array); /// @@ -318,7 +341,7 @@ public ImmutableArray Insert(int index, T item) /// The index at which to insert the value. /// The elements to insert. /// The new immutable collection. - public ImmutableArray InsertRange(int index, IEnumerable items) + internal ImmutableArray InsertRange(int index, IEnumerable items) { var self = this; self.ThrowNullRefIfNotInitialized(); @@ -365,6 +388,12 @@ public ImmutableArray InsertRange(int index, IEnumerable items) return new ImmutableArray(tmp); } + /// + /// Inserts the specified values at the specified index. + /// + /// The index at which to insert the value. + /// The elements to insert. + /// The new immutable collection. public ImmutableArray InsertRange(int index, T[] items) { var self = this; @@ -401,51 +430,6 @@ public ImmutableArray InsertRange(int index, T[] items) /// The index at which to insert the value. /// The elements to insert. /// The new immutable collection. - public ImmutableArray InsertRange(int index, ImmutableArray items) - { - var self = this; - items.ThrowNullRefIfNotInitialized(); - return self.InsertRange(index, items.array!); - } - - /// - /// Returns a new array with the specified value inserted at the end. - /// - /// The item to insert at the end of the array. - /// A new array. - public ImmutableArray Add(T item) - { - var self = this; - if (self.Length == 0) - { - return ImmutableArray.Create(item); - } - - return self.Insert(self.Length, item); - } - - /// - /// Adds the specified values to this list. - /// - /// The values to add. - /// A new list with the elements added. - public ImmutableArray AddRange(IEnumerable items) - { - var self = this; - return self.InsertRange(self.Length, items); - } - - /// - /// Adds the specified values to this list. - /// - /// The values to add. - /// A new list with the elements added. - public ImmutableArray AddRange(ImmutableArray items) - { - var self = this; - return self.InsertRange(self.Length, items); - } - public ImmutableArray InsertRange(int index, ReadOnlySpan items) { var self = this; @@ -475,12 +459,38 @@ public ImmutableArray InsertRange(int index, ReadOnlySpan items) return new ImmutableArray(tmp); } + /// + /// Returns a new array with the specified value inserted at the end. + /// + /// The item to insert at the end of the array. + /// A new array. + public ImmutableArray Add(T item) + { + var self = this; + if (self.Length == 0) + { + return ImmutableArray.Create(item); + } + + return self.Insert(self.Length, item); + } + + /// + /// Adds the specified values to this list. + /// + /// The values to add. + /// A new list with the elements added. public ImmutableArray AddRange(ReadOnlySpan items) { var self = this; return self.InsertRange(self.Length, items); } + /// + /// Adds the specified values to this list. + /// + /// The values to add. + /// A new list with the elements added. public ImmutableArray AddRange(params T[] items) { var self = this; @@ -587,7 +597,7 @@ public ImmutableArray RemoveAt(int index) /// The 0-based index into the array for the element to omit from the returned array. /// The number of elements to remove. /// The new array. - public ImmutableArray RemoveRange(int index, int length) + internal ImmutableArray RemoveRange(int index, int length) { var self = this; self.ThrowNullRefIfNotInitialized(); @@ -605,18 +615,6 @@ public ImmutableArray RemoveRange(int index, int length) return new ImmutableArray(tmp); } - /// - /// Removes the specified values from this list. - /// - /// The items to remove if matches are found in this list. - /// - /// A new list with the elements removed. - /// - public ImmutableArray RemoveRange(IEnumerable items) - { - return this.RemoveRange(items, EqualityComparer.Default); - } - /// /// Removes the specified values from this list. /// @@ -628,7 +626,7 @@ public ImmutableArray RemoveRange(IEnumerable items) /// /// A new list with the elements removed. /// - public ImmutableArray RemoveRange(IEnumerable items, IEqualityComparer? equalityComparer) + internal ImmutableArray RemoveRange(IEnumerable items, IEqualityComparer? equalityComparer) { var self = this; self.ThrowNullRefIfNotInitialized(); @@ -638,7 +636,7 @@ public ImmutableArray RemoveRange(IEnumerable items, IEqualityComparer? foreach (var item in items) { int index = self.IndexOf(item, 0, self.Length, equalityComparer); - while (index >= 0 && !indicesToRemove.Add(index) && index + 1 < self.Length) + while (index >= 0 && indicesToRemove.Add(index) && index + 1 < self.Length) { // This is a duplicate of one we've found. Try hard to find another instance in the list to remove. index = self.IndexOf(item, index + 1, equalityComparer); @@ -648,16 +646,62 @@ public ImmutableArray RemoveRange(IEnumerable items, IEqualityComparer? return self.RemoveAtRange(indicesToRemove); } + /// + /// Forms a slice out of the current starting at a specified index for a specified length. + /// + /// The index at which to begin this slice. + /// The desired length for the slice. + /// A that consists of length elements from the current starting at start. + public ImmutableArray Slice(int start, int length) + { + var self = this; + self.ThrowNullRefIfNotInitialized(); + return ImmutableArray.Create(self, start, length); + } + /// /// Removes the specified values from this list. /// /// The items to remove if matches are found in this list. + /// + /// The equality comparer to use in the search. + /// /// /// A new list with the elements removed. /// - public ImmutableArray RemoveRange(ImmutableArray items) + public ImmutableArray RemoveRange(ReadOnlySpan items, IEqualityComparer? equalityComparer = null) { - return this.RemoveRange(items, EqualityComparer.Default); + var self = this; + self.ThrowNullRefIfNotInitialized(); + if (items.IsEmpty || self.IsEmpty) + { + return self; + } + + SortedSet? indicesToRemove = null; + foreach (T item in items) + { + int index = self.IndexOf(item, 0, equalityComparer); + if (index < 0) + { + continue; + } + if (indicesToRemove == null) // indicesToRemove is not initialized so current item is valid + { + indicesToRemove = new SortedSet {index}; + } + else if (!indicesToRemove.Add(index)) // If here we have found pre existing index, just skip current 'item'; otherwise current 'item' must be a new item and we can skip checking indicesToRemove.Add() below + { + continue; + } + + while (index < self.Length - 1 && (index = self.IndexOf(item, index + 1, equalityComparer)) >= 0) + { + indicesToRemove.Add(index); + } + } + + return indicesToRemove == null ? self : self.RemoveAtRange(indicesToRemove); } /// @@ -670,24 +714,50 @@ public ImmutableArray RemoveRange(ImmutableArray items) /// /// A new list with the elements removed. /// - public ImmutableArray RemoveRange(ImmutableArray items, IEqualityComparer? equalityComparer) + public ImmutableArray RemoveRange(T[] items, IEqualityComparer? equalityComparer = null) { var self = this; - Requires.NotNull(items.array!, nameof(items)); + self.ThrowNullRefIfNotInitialized(); + Requires.NotNull(items, nameof(items)); - if (items.IsEmpty) + if (items.Length == 0 || self.IsEmpty) { - self.ThrowNullRefIfNotInitialized(); return self; } - else if (items.Length == 1) - { - return self.Remove(items[0], equalityComparer); - } - else + + List? indicesToRemove = null; + for (int i = 0; i < self.Length; i++) { - return self.RemoveRange(items.array!, equalityComparer); + T selfItem = self.array![i]; + bool found = false; + + if (equalityComparer == null || equalityComparer == EqualityComparer.Default) + { + if (Array.IndexOf(items, selfItem) >= 0) + { + found = true; + } + } + else + { + foreach (T item in items) + { + if (equalityComparer.Equals(selfItem, item)) + { + found = true; + break; + } + } + } + + if (found) + { + indicesToRemove ??= new List(); + indicesToRemove.Add(i); + } } + + return indicesToRemove == null ? self : self.RemoveAtRange(indicesToRemove); } /// @@ -837,6 +907,19 @@ public IEnumerable OfType() return self.array.OfType(); } + /// + /// Copies the elements of current to an . + /// + /// The that is the destination of the elements copied from current . + public void CopyTo(Span destination) + { + var self = this; + self.ThrowInvalidOperationIfNotInitialized(); + Requires.Range(self.Length <= destination.Length, nameof(destination)); + + self.AsSpan().CopyTo(destination); + } + #region Explicit interface methods void IList.Insert(int index, T item) @@ -891,7 +974,7 @@ IImmutableList IImmutableList.AddRange(IEnumerable items) { var self = this; self.ThrowInvalidOperationIfNotInitialized(); - return self.AddRange(items); + return self.InsertRange(self.Length, items); } /// diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayBuilderTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayBuilderTest.cs index 5a70cb43e1e31..36fe700831de0 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayBuilderTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayBuilderTest.cs @@ -66,6 +66,7 @@ public void NormalConstructionRefType() Assert.Equal(7, builder[2].Data); } + /* [Fact] public void AddRangeIEnumerable() { @@ -85,6 +86,7 @@ public void AddRangeIEnumerable() Assert.Equal(Enumerable.Range(1, 4), builder); } + */ [Fact] public void Add() @@ -126,6 +128,7 @@ public void AddRangeBuilder() Assert.Equal(new[] { 1, 2 }, builder1); } + /* [Fact] public void AddRangeImmutableArray() { @@ -150,6 +153,7 @@ public void AddRangeImmutableArray() builder2.AddRange(default(ImmutableArray)); AssertExtensions.Throws("items", () => builder2.AddRange((ImmutableArray.Builder)null)); } + */ [Fact] public void AddRangeDerivedArray() @@ -159,6 +163,7 @@ public void AddRangeDerivedArray() Assert.Equal(new[] { "a", "b" }, builder); } + /* [Fact] public void AddRangeDerivedImmutableArray() { @@ -166,7 +171,9 @@ public void AddRangeDerivedImmutableArray() builder.AddRange(new[] { "a", "b" }.ToImmutableArray()); Assert.Equal(new[] { "a", "b" }, builder); } + */ + /* [Fact] public void AddRangeDerivedBuilder() { @@ -177,6 +184,7 @@ public void AddRangeDerivedBuilder() builderBase.AddRange(builder); Assert.Equal(new[] { "a", "b" }, builderBase); } + */ [Fact] public void Contains() @@ -214,7 +222,7 @@ public void LastIndexOf() public void Insert() { var builder = new ImmutableArray.Builder(); - builder.AddRange(1, 2, 3); + builder.AddRange(new ReadOnlySpan(new []{1, 2, 3})); builder.Insert(1, 4); builder.Insert(4, 5); Assert.Equal(new[] { 1, 4, 2, 3, 5 }, builder); @@ -226,7 +234,7 @@ public void Insert() public void Remove() { var builder = new ImmutableArray.Builder(); - builder.AddRange(1, 2, 3, 4); + builder.AddRange(new ReadOnlySpan(new[] { 1, 2, 3, 4 })); Assert.True(builder.Remove(1)); Assert.False(builder.Remove(6)); Assert.Equal(new[] { 2, 3, 4 }, builder); @@ -242,7 +250,7 @@ public void Remove() public void RemoveAt() { var builder = new ImmutableArray.Builder(); - builder.AddRange(1, 2, 3, 4); + builder.AddRange(new ReadOnlySpan(new[] { 1, 2, 3, 4 })); builder.RemoveAt(0); AssertExtensions.Throws("index", () => builder.RemoveAt(-1)); AssertExtensions.Throws("index", () => builder.RemoveAt(3)); @@ -259,7 +267,7 @@ public void RemoveAt() public void ReverseContents() { var builder = new ImmutableArray.Builder(); - builder.AddRange(1, 2, 3, 4); + builder.AddRange(new ReadOnlySpan(new[] { 1, 2, 3, 4 })); builder.Reverse(); Assert.Equal(new[] { 4, 3, 2, 1 }, builder); @@ -284,7 +292,7 @@ public void ReverseContents() public void Sort() { var builder = new ImmutableArray.Builder(); - builder.AddRange(2, 4, 1, 3); + builder.AddRange(new ReadOnlySpan(new[] { 2, 4, 1, 3 })); builder.Sort(); Assert.Equal(new[] { 1, 2, 3, 4 }, builder); } @@ -297,7 +305,7 @@ public void Sort_Comparison() builder.Sort((x, y) => y.CompareTo(x)); Assert.Equal(Array.Empty(), builder); - builder.AddRange(2, 4, 1, 3); + builder.AddRange(new ReadOnlySpan(new[] { 2, 4, 1, 3 })); builder.Sort((x, y) => y.CompareTo(x)); Assert.Equal(new[] { 4, 3, 2, 1 }, builder); @@ -351,7 +359,7 @@ public void SortOneElementArray() public void SortRange() { var builder = new ImmutableArray.Builder(); - builder.AddRange(2, 4, 1, 3); + builder.AddRange(new[] { 2, 4, 1, 3 }.AsSpan()); AssertExtensions.Throws("index", () => builder.Sort(-1, 2, Comparer.Default)); AssertExtensions.Throws("count", () => builder.Sort(1, 4, Comparer.Default)); AssertExtensions.Throws("count", () => builder.Sort(0, -1, Comparer.Default)); @@ -368,8 +376,8 @@ public void SortComparer() { var builder1 = new ImmutableArray.Builder(); var builder2 = new ImmutableArray.Builder(); - builder1.AddRange("c", "B", "a"); - builder2.AddRange("c", "B", "a"); + builder1.AddRange(new []{"c", "B", "a"}.AsSpan()); + builder2.AddRange(new []{"c", "B", "a"}.AsSpan()); builder1.Sort(StringComparer.OrdinalIgnoreCase); builder2.Sort(StringComparer.Ordinal); Assert.Equal(new[] { "a", "B", "c" }, builder1); @@ -407,7 +415,7 @@ public void Count() public void CountContract() { var builder = new ImmutableArray.Builder(100); - builder.AddRange(Enumerable.Range(1, 100)); + builder.AddRange(Enumerable.Range(1, 100).ToArray().AsSpan()); builder.Count = 10; Assert.Equal(Enumerable.Range(1, 10), builder); builder.Count = 100; @@ -438,7 +446,7 @@ public void IndexSetter() public void ToImmutable() { var builder = new ImmutableArray.Builder(); - builder.AddRange(1, 2, 3); + builder.AddRange(new []{1, 2, 3}.AsSpan()); ImmutableArray array = builder.ToImmutable(); Assert.Equal(1, array[0]); @@ -458,7 +466,7 @@ public void ToImmutable() public void ToImmutableArray() { var builder = new ImmutableArray.Builder(); - builder.AddRange(0, 1, 2); + builder.AddRange(new []{0, 1, 2}.AsSpan()); var array = builder.ToImmutableArray(); Assert.Equal(0, array[0]); @@ -521,7 +529,7 @@ public void Enumerator() Assert.False(enumerator.MoveNext()); var manyElements = new ImmutableArray.Builder(3); - manyElements.AddRange(1, 2, 3); + manyElements.AddRange(new []{1, 2, 3}.AsSpan()); enumerator = manyElements.GetEnumerator(); Assert.True(enumerator.MoveNext()); @@ -542,7 +550,7 @@ public void IEnumerator() Assert.False(enumerator.MoveNext()); var manyElements = new ImmutableArray.Builder(3); - manyElements.AddRange(1, 2, 3); + manyElements.AddRange(new []{1, 2, 3}.AsSpan()); enumerator = ((IEnumerable)manyElements).GetEnumerator(); Assert.True(enumerator.MoveNext()); @@ -732,7 +740,7 @@ public void DebuggerAttributesValid() { DebuggerAttributes.ValidateDebuggerDisplayReferences(ImmutableArray.CreateBuilder()); ImmutableArray.Builder builder = ImmutableArray.CreateBuilder(4); - builder.AddRange("One", "Two", "Three", "Four"); + builder.AddRange(new []{"One", "Two", "Three", "Four"}.AsSpan()); DebuggerAttributeInfo info = DebuggerAttributes.ValidateDebuggerTypeProxyProperties(builder); PropertyInfo itemProperty = info.Properties.Single(pr => pr.GetCustomAttribute().State == DebuggerBrowsableState.RootHidden); string[] items = itemProperty.GetValue(info.Instance) as string[]; diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs index 7349cefa67b5c..4ca0bd88a3a09 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs @@ -1006,6 +1006,7 @@ public void Add(IEnumerable source, IEnumerable items) } } + /* [Theory] [MemberData(nameof(AddData))] public void AddRange(IEnumerable source, IEnumerable items) @@ -1019,6 +1020,7 @@ public void AddRange(IEnumerable source, IEnumerable items) Assert.Equal(source, array); // Make sure the original array wasn't affected. }); } + */ public static IEnumerable AddData() { @@ -1036,6 +1038,7 @@ public static IEnumerable AddData() yield return new object[] { s_oneElement, s_empty }; } + /* [Theory] [MemberData(nameof(Int32EnumerableData))] public void AddRangeInvalid(IEnumerable source) @@ -1050,6 +1053,7 @@ public void AddRangeInvalid(IEnumerable source) TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.AddRange(s_emptyDefault)); TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.AddRange((IEnumerable)s_emptyDefault)); } + */ [Theory] [MemberData(nameof(InsertData))] @@ -1091,6 +1095,7 @@ public void InsertDefaultInvalid(int index) TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.Insert(index, 10)); } + /* [Theory] [MemberData(nameof(Int32EnumerableData))] public void InsertRangeInvalid(IEnumerable source) @@ -1103,17 +1108,19 @@ public void InsertRangeInvalid(IEnumerable source) AssertExtensions.Throws("index", () => array.InsertRange(array.Length + 1, (IEnumerable)s_oneElement)); AssertExtensions.Throws("index", () => array.InsertRange(-1, (IEnumerable)s_oneElement)); } + */ + /* [Theory] [MemberData(nameof(Int32EnumerableData))] public void InsertRangeDefaultInvalid(IEnumerable items) { var array = items.ToImmutableArray(); - + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.InsertRange(1, items)); TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.InsertRange(-1, items)); TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.InsertRange(0, items)); - + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.InsertRange(1, array)); TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.InsertRange(-1, array)); TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.InsertRange(0, array)); @@ -1129,7 +1136,9 @@ public void InsertRangeDefaultInvalid(IEnumerable items) Assert.Throws(() => array.InsertRange(0, (IEnumerable)s_emptyDefault)); } + */ + /* [Theory] [MemberData(nameof(InsertRangeData))] public void InsertRange(IEnumerable source, int index, IEnumerable items) @@ -1153,6 +1162,7 @@ public void InsertRange(IEnumerable source, int index, IEnumerable ite } }); } + */ public static IEnumerable InsertRangeData() { @@ -1259,6 +1269,7 @@ public void RemoveDefaultInvalid() }); } + /* [Theory] [MemberData(nameof(RemoveRangeIndexLengthData))] public void RemoveRangeIndexLength(IEnumerable source, int index, int length) @@ -1267,6 +1278,7 @@ public void RemoveRangeIndexLength(IEnumerable source, int index, int lengt var expected = source.Take(index).Concat(source.Skip(index + length)); Assert.Equal(expected, array.RemoveRange(index, length)); } + */ public static IEnumerable RemoveRangeIndexLengthData() { @@ -1279,6 +1291,7 @@ public static IEnumerable RemoveRangeIndexLengthData() yield return new object[] { new[] { 1, 2, 3, 4 }, 2, 2 }; } + /* [Theory] [MemberData(nameof(Int32EnumerableData))] public void RemoveRangeIndexLengthInvalid(IEnumerable source) @@ -1290,7 +1303,9 @@ public void RemoveRangeIndexLengthInvalid(IEnumerable source) AssertExtensions.Throws("length", () => array.RemoveRange(0, -1)); AssertExtensions.Throws("length", () => array.RemoveRange(0, array.Length + 1)); } + */ + /* [Theory] [InlineData(-1, 0)] [InlineData(0, -1)] @@ -1300,7 +1315,9 @@ public void RemoveRangeIndexLengthDefaultInvalid(int index, int length) { TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(index, length)); } + */ + /* Strange behavior ! [Theory] [MemberData(nameof(RemoveRangeEnumerableData))] public void RemoveRangeEnumerable(IEnumerable source, IEnumerable items, IEqualityComparer comparer) @@ -1310,17 +1327,18 @@ public void RemoveRangeEnumerable(IEnumerable source, IEnumerable item seed: source.ToImmutableArray(), func: (a, i) => a.Remove(i, comparer)); - Assert.Equal(expected, array.RemoveRange(items, comparer)); // Enumerable overload - Assert.Equal(expected, array.RemoveRange(items.ToImmutableArray(), comparer)); // Struct overload + // Assert.Equal(expected, array.RemoveRange(items, comparer)); // Enumerable overload + // Assert.Equal(expected, array.RemoveRange(items.ToImmutableArray(), comparer)); // Struct overload Assert.Equal(expected, ((IImmutableList)array).RemoveRange(items, comparer)); if (comparer == null || comparer == EqualityComparer.Default) { - Assert.Equal(expected, array.RemoveRange(items)); // Enumerable overload - Assert.Equal(expected, array.RemoveRange(items.ToImmutableArray())); // Struct overload + // Assert.Equal(expected, array.RemoveRange(items)); // Enumerable overload + // Assert.Equal(expected, array.RemoveRange(items.ToImmutableArray())); // Struct overload Assert.Equal(expected, ((IImmutableList)array).RemoveRange(items)); } } + */ public static IEnumerable RemoveRangeEnumerableData() { @@ -1357,49 +1375,50 @@ public void RemoveRangeEnumerableInvalid(IEnumerable source) Assert.All(SharedEqualityComparers(), comparer => { // Enumerable overloads, lhs is default - TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(source)); - TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(source, comparer)); + // TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(source)); + // TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(source, comparer)); Assert.Throws(() => ((IImmutableList)s_emptyDefault).RemoveRange(source)); Assert.Throws(() => ((IImmutableList)s_emptyDefault).RemoveRange(source, comparer)); // Struct overloads, lhs is default - TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(array)); - TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(array, comparer)); + // TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(array)); + // TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(array, comparer)); // Struct overloads, rhs is default - AssertExtensions.Throws("items", () => array.RemoveRange(s_emptyDefault)); - AssertExtensions.Throws("items", () => array.RemoveRange(s_emptyDefault, comparer)); + // AssertExtensions.Throws("items", () => array.RemoveRange(s_emptyDefault)); + // AssertExtensions.Throws("items", () => array.RemoveRange(s_emptyDefault, comparer)); // Enumerable overloads, rhs is default - Assert.Throws(() => array.RemoveRange((IEnumerable)s_emptyDefault)); - Assert.Throws(() => array.RemoveRange((IEnumerable)s_emptyDefault, comparer)); + // Assert.Throws(() => array.RemoveRange((IEnumerable)s_emptyDefault)); + // Assert.Throws(() => array.RemoveRange((IEnumerable)s_emptyDefault, comparer)); Assert.Throws(() => ((IImmutableList)array).RemoveRange(s_emptyDefault)); Assert.Throws(() => ((IImmutableList)array).RemoveRange(s_emptyDefault, comparer)); // Struct overloads, both sides are default - AssertExtensions.Throws("items", () => s_emptyDefault.RemoveRange(s_emptyDefault)); - AssertExtensions.Throws("items", () => s_emptyDefault.RemoveRange(s_emptyDefault, comparer)); + // AssertExtensions.Throws("items", () => s_emptyDefault.RemoveRange(s_emptyDefault)); + // AssertExtensions.Throws("items", () => s_emptyDefault.RemoveRange(s_emptyDefault, comparer)); // Enumerable overloads, both sides are default - TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange((IEnumerable)s_emptyDefault)); - TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange((IEnumerable)s_emptyDefault, comparer)); + // TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange((IEnumerable)s_emptyDefault)); + // TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange((IEnumerable)s_emptyDefault, comparer)); Assert.Throws(() => ((IImmutableList)s_emptyDefault).RemoveRange(s_emptyDefault)); Assert.Throws(() => ((IImmutableList)s_emptyDefault).RemoveRange(s_emptyDefault, comparer)); // Enumerable overloads, rhs is null - AssertExtensions.Throws("items", () => array.RemoveRange(items: null)); - AssertExtensions.Throws("items", () => array.RemoveRange(items: null, equalityComparer: comparer)); + // AssertExtensions.Throws("items", () => array.RemoveRange(items: null)); + // AssertExtensions.Throws("items", () => array.RemoveRange(items: null, equalityComparer: comparer)); AssertExtensions.Throws("items", () => ((IImmutableList)array).RemoveRange(items: null)); AssertExtensions.Throws("items", () => ((IImmutableList)array).RemoveRange(items: null, equalityComparer: comparer)); // Enumerable overloads, lhs is default and rhs is null - TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(items: null)); - TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(items: null, equalityComparer: comparer)); + // TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(items: null)); + // TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(items: null, equalityComparer: comparer)); Assert.Throws(() => ((IImmutableList)s_emptyDefault).RemoveRange(items: null)); Assert.Throws(() => ((IImmutableList)s_emptyDefault).RemoveRange(items: null, equalityComparer: comparer)); }); } + /* [Fact] public void RemoveRangeEnumerableRegression() { @@ -1416,6 +1435,7 @@ public void RemoveRangeEnumerableRegression() Assert.Equal(oneElementBoxed, oneElementBoxed); } + */ [Theory] [MemberData(nameof(RemoveAllData))] From 10ed570f02be95ea751637cbb51e9c4d803b4e32 Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Thu, 18 Nov 2021 18:54:18 +0800 Subject: [PATCH 03/29] Eliminate range based method compilation for .netstandard2.0 and net461. --- .../ref/System.Collections.Immutable.cs | 4 +++- .../src/System/Collections/Immutable/ImmutableArray_1.cs | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs b/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs index 1fd9c3dc97aa8..c45894a203d6d 100644 --- a/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs +++ b/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs @@ -125,7 +125,9 @@ public static partial class ImmutableArray public System.ReadOnlyMemory AsMemory() { throw null; } public System.ReadOnlySpan AsSpan() { throw null; } public System.ReadOnlySpan AsSpan(int start, int length) { throw null; } - // public System.ReadOnlySpan AsSpan(System.Range range) { throw null; } +#if !NETSTANDARD2_0 && !NET461 + public System.ReadOnlySpan AsSpan(System.Range range) { throw null; } +#endif public System.Collections.Immutable.ImmutableArray< #nullable disable TOther diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs index c036835edb949..c924a1fc1c829 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs @@ -104,7 +104,7 @@ T IReadOnlyList.this[int index] /// The representation of the public ReadOnlySpan AsSpan(int start, int length) => new ReadOnlySpan(array, start, length); - /* +#if !NETSTANDARD2_0 && !NET461 public ReadOnlySpan AsSpan(Range range) { var self = this; @@ -113,7 +113,7 @@ public ReadOnlySpan AsSpan(Range range) (int start, int length) = range.GetOffsetAndLength(self.Length); return new ReadOnlySpan(self.array, start, length); } - */ +#endif public ReadOnlyMemory AsMemory() => new ReadOnlyMemory(array); From 89810adaca0832c6c366f82523c8267c7ae220fd Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Tue, 30 Nov 2021 15:17:15 +0800 Subject: [PATCH 04/29] issue-22928 Refine to use common code. --- .../ref/System.Collections.Immutable.cs | 17 + .../Immutable/ImmutableArray_1.Builder.cs | 144 ++++++- .../Immutable/ImmutableArray_1.Minimal.cs | 2 +- .../Collections/Immutable/ImmutableArray_1.cs | 384 ++++++++++-------- .../tests/ImmutableArrayBuilderTest.cs | 38 +- .../tests/ImmutableArrayTest.cs | 64 +-- 6 files changed, 394 insertions(+), 255 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs b/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs index c45894a203d6d..7e76b1b5a6193 100644 --- a/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs +++ b/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs @@ -120,6 +120,8 @@ public static partial class ImmutableArray bool System.Collections.IList.IsReadOnly { get { throw null; } } object? System.Collections.IList.this[int index] { get { throw null; } set { } } public System.Collections.Immutable.ImmutableArray Add(T item) { throw null; } + public System.Collections.Immutable.ImmutableArray AddRange(System.Collections.Generic.IEnumerable items) { throw null; } + public System.Collections.Immutable.ImmutableArray AddRange(System.Collections.Immutable.ImmutableArray items) { throw null; } public System.Collections.Immutable.ImmutableArray AddRange(System.ReadOnlySpan items) { throw null; } public System.Collections.Immutable.ImmutableArray AddRange(params T[] items) { throw null; } public System.ReadOnlyMemory AsMemory() { throw null; } @@ -159,6 +161,8 @@ public void CopyTo(System.Span destination) { } public int IndexOf(T item, int startIndex, int count) { throw null; } public int IndexOf(T item, int startIndex, int count, System.Collections.Generic.IEqualityComparer? equalityComparer) { throw null; } public System.Collections.Immutable.ImmutableArray Insert(int index, T item) { throw null; } + public System.Collections.Immutable.ImmutableArray InsertRange(int index, System.Collections.Generic.IEnumerable items) { throw null; } + public System.Collections.Immutable.ImmutableArray InsertRange(int index, System.Collections.Immutable.ImmutableArray items) { throw null; } public System.Collections.Immutable.ImmutableArray InsertRange(int index, T[] items) { throw null; } public System.Collections.Immutable.ImmutableArray InsertRange(int index, System.ReadOnlySpan items) { throw null; } public ref readonly T ItemRef(int index) { throw null; } @@ -175,6 +179,11 @@ public void CopyTo(System.Span destination) { } public System.Collections.Immutable.ImmutableArray Remove(T item, System.Collections.Generic.IEqualityComparer? equalityComparer) { throw null; } public System.Collections.Immutable.ImmutableArray RemoveAll(System.Predicate match) { throw null; } public System.Collections.Immutable.ImmutableArray RemoveAt(int index) { throw null; } + public System.Collections.Immutable.ImmutableArray RemoveRange(System.Collections.Generic.IEnumerable items) { throw null; } + public System.Collections.Immutable.ImmutableArray RemoveRange(System.Collections.Generic.IEnumerable items, System.Collections.Generic.IEqualityComparer? equalityComparer) { throw null; } + public System.Collections.Immutable.ImmutableArray RemoveRange(System.Collections.Immutable.ImmutableArray items) { throw null; } + public System.Collections.Immutable.ImmutableArray RemoveRange(System.Collections.Immutable.ImmutableArray items, System.Collections.Generic.IEqualityComparer? equalityComparer) { throw null; } + public System.Collections.Immutable.ImmutableArray RemoveRange(int index, int length) { throw null; } public System.Collections.Immutable.ImmutableArray RemoveRange(System.ReadOnlySpan items, System.Collections.Generic.IEqualityComparer? equalityComparer = null) { throw null; } public System.Collections.Immutable.ImmutableArray RemoveRange(T[] items, System.Collections.Generic.IEqualityComparer? equalityComparer = null) { throw null; } public System.Collections.Immutable.ImmutableArray Slice(int start, int length) { throw null; } @@ -224,7 +233,15 @@ internal Builder() { } public T this[int index] { get { throw null; } set { } } bool System.Collections.Generic.ICollection.IsReadOnly { get { throw null; } } public void Add(T item) { } + public void AddRange(System.Collections.Generic.IEnumerable items) { } + public void AddRange(System.Collections.Immutable.ImmutableArray items) { } + public void AddRange(System.Collections.Immutable.ImmutableArray items, int length) { } public void AddRange(System.Collections.Immutable.ImmutableArray.Builder items) { } + public void AddRange(params T[] items) { } + public void AddRange(T[] items, int length) { } + public void AddRange(System.Collections.Immutable.ImmutableArray items) where TDerived : T { } + public void AddRange(System.Collections.Immutable.ImmutableArray.Builder items) where TDerived : T { } + public void AddRange(TDerived[] items) where TDerived : T { } public void AddRange(System.ReadOnlySpan items) { } public void AddRange(System.ReadOnlySpan items) where TDerived : T { } public void Clear() { } diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs index 94d2213060b8b..28cfa5924ccad 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs @@ -255,14 +255,93 @@ public void Add(T item) /// Adds the specified items to the end of the array. /// /// The items. - public void AddRange(ReadOnlySpan items) where TDerived : T + public void AddRange(IEnumerable items) { - int offset = this.Count; + Requires.NotNull(items, nameof(items)); + + int count; + if (items.TryGetCount(out count)) + { + this.EnsureCapacity(this.Count + count); + + if (items.TryCopyTo(_elements, _count)) + { + _count += count; + return; + } + } + + foreach (var item in items) + { + this.Add(item); + } + } + + /// + /// Adds the specified items to the end of the array. + /// + /// The items. + public void AddRange(params T[] items) + { + Requires.NotNull(items, nameof(items)); + + var offset = this.Count; this.Count += items.Length; - for (int i = 0; i < items.Length; i++) + Array.Copy(items, 0, _elements, offset, items.Length); + } + + /// + /// Adds the specified items to the end of the array. + /// + /// The items. + public void AddRange(TDerived[] items) where TDerived : T + { + Requires.NotNull(items, nameof(items)); + + var offset = this.Count; + this.Count += items.Length; + + Array.Copy(items, 0, _elements, offset, items.Length); + } + + /// + /// Adds the specified items to the end of the array. + /// + /// The items. + /// The number of elements from the source array to add. + public void AddRange(T[] items, int length) + { + Requires.NotNull(items, nameof(items)); + Requires.Range(length >= 0 && length <= items.Length, nameof(length)); + + var offset = this.Count; + this.Count += length; + + Array.Copy(items, 0, _elements, offset, length); + } + + /// + /// Adds the specified items to the end of the array. + /// + /// The items. + public void AddRange(ImmutableArray items) + { + this.AddRange(items, items.Length); + } + + /// + /// Adds the specified items to the end of the array. + /// + /// The items. + /// The number of elements from the source array to add. + public void AddRange(ImmutableArray items, int length) + { + Requires.Range(length >= 0, nameof(length)); + + if (items.array != null) { - _elements[offset + i] = items[i]; + this.AddRange(items.array, length); } } @@ -278,6 +357,33 @@ public void AddRange(ReadOnlySpan items) items.CopyTo(new Span(_elements, offset, items.Length)); } + /// + /// Adds the specified items to the end of the array. + /// + /// The items. + public void AddRange(ReadOnlySpan items) where TDerived : T + { + int offset = this.Count; + this.Count += items.Length; + + for (int i = 0; i < items.Length; i++) + { + _elements[offset + i] = items[i]; + } + } + + /// + /// Adds the specified items to the end of the array. + /// + /// The items. + public void AddRange(ImmutableArray items) where TDerived : T + { + if (items.array != null) + { + this.AddRange(items.array); + } + } + /// /// Adds the specified items to the end of the array. /// @@ -288,6 +394,16 @@ public void AddRange(Builder items) this.AddRange(items._elements, items.Count); } + /// + /// Adds the specified items to the end of the array. + /// + /// The items. + public void AddRange(ImmutableArray.Builder items) where TDerived : T + { + Requires.NotNull(items, nameof(items)); + this.AddRange(items._elements, items.Count); + } + /// /// Removes the specified element. /// @@ -360,16 +476,6 @@ public void CopyTo(T[] array, int index) Array.Copy(_elements, 0, array, index, this.Count); } - /// - /// Copies the current contents to the specified . - /// - /// The to copy to. - public void CopyTo(Span destination) - { - Requires.Range(this.Count <= destination.Length, nameof(destination)); - new ReadOnlySpan(_elements).CopyTo(destination); - } - /// /// Resizes the array to accommodate the specified capacity requirement. /// @@ -626,6 +732,16 @@ public void Sort(int index, int count, IComparer? comparer) } } + /// + /// Copies the current contents to the specified . + /// + /// The to copy to. + public void CopyTo(Span destination) + { + Requires.Range(this.Count <= destination.Length, nameof(destination)); + new ReadOnlySpan(_elements).CopyTo(destination); + } + /// /// Returns an enumerator for the contents of the array. /// diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Minimal.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Minimal.cs index 4bf691a853782..560fd76d18adf 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Minimal.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Minimal.cs @@ -269,7 +269,7 @@ public ImmutableArray.Builder ToBuilder() } var builder = new Builder(self.Length); - builder.AddRange(self.AsSpan()); + builder.AddRange(self); return builder; } diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs index c924a1fc1c829..bba5e8116cbf2 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs @@ -90,31 +90,8 @@ T IReadOnlyList.this[int index] } } - /// - /// Create a over current - /// - /// The representation of the public ReadOnlySpan AsSpan() => new ReadOnlySpan(array); - /// - /// Creates a over the portion of current beginning at a specified position for a specified length. - /// - /// The index at which to begin the span. - /// The number of items in the span. - /// The representation of the - public ReadOnlySpan AsSpan(int start, int length) => new ReadOnlySpan(array, start, length); - -#if !NETSTANDARD2_0 && !NET461 - public ReadOnlySpan AsSpan(Range range) - { - var self = this; - self.ThrowNullRefIfNotInitialized(); - - (int start, int length) = range.GetOffsetAndLength(self.Length); - return new ReadOnlySpan(self.array, start, length); - } -#endif - public ReadOnlyMemory AsMemory() => new ReadOnlyMemory(array); /// @@ -341,7 +318,7 @@ public ImmutableArray Insert(int index, T item) /// The index at which to insert the value. /// The elements to insert. /// The new immutable collection. - internal ImmutableArray InsertRange(int index, IEnumerable items) + public ImmutableArray InsertRange(int index, IEnumerable items) { var self = this; self.ThrowNullRefIfNotInitialized(); @@ -394,69 +371,23 @@ internal ImmutableArray InsertRange(int index, IEnumerable items) /// The index at which to insert the value. /// The elements to insert. /// The new immutable collection. - public ImmutableArray InsertRange(int index, T[] items) + public ImmutableArray InsertRange(int index, ImmutableArray items) { var self = this; self.ThrowNullRefIfNotInitialized(); + items.ThrowNullRefIfNotInitialized(); Requires.Range(index >= 0 && index <= self.Length, nameof(index)); - Requires.NotNull(items, nameof(items)); - if (items.Length == 0) - { - return self; - } - if (self.Length == 0) - { - return new ImmutableArray(items); - } - - var tmp = new T[self.Length + items.Length]; - if (index != 0) + if (self.IsEmpty) { - Array.Copy(self.array!, tmp, index); + return items; } - Array.Copy(items, 0, tmp, index, items.Length); - if (index != self.Length) - { - Array.Copy(self.array!, index, tmp, index + items.Length, self.Length - index); - } - - return new ImmutableArray(tmp); - } - - /// - /// Inserts the specified values at the specified index. - /// - /// The index at which to insert the value. - /// The elements to insert. - /// The new immutable collection. - public ImmutableArray InsertRange(int index, ReadOnlySpan items) - { - var self = this; - self.ThrowNullRefIfNotInitialized(); - Requires.Range(index >= 0 && index <= self.Length, nameof(index)); - - if (items.IsEmpty) + else if (items.IsEmpty) { return self; } - if (self.Length == 0) - { - return items.ToImmutableArray(); - } - var tmp = new T[self.Length + items.Length]; - if (index != 0) - { - Array.Copy(self.array!, tmp, index); - } - items.CopyTo(new Span(tmp, index, items.Length)); - if (index != self.Length) - { - Array.Copy(self.array!, index, tmp, index + items.Length, self.Length - index); - } - - return new ImmutableArray(tmp); + return self.InsertSpanRangeInternal(index, items.AsSpan()); } /// @@ -480,7 +411,7 @@ public ImmutableArray Add(T item) /// /// The values to add. /// A new list with the elements added. - public ImmutableArray AddRange(ReadOnlySpan items) + public ImmutableArray AddRange(IEnumerable items) { var self = this; return self.InsertRange(self.Length, items); @@ -491,7 +422,7 @@ public ImmutableArray AddRange(ReadOnlySpan items) /// /// The values to add. /// A new list with the elements added. - public ImmutableArray AddRange(params T[] items) + public ImmutableArray AddRange(ImmutableArray items) { var self = this; return self.InsertRange(self.Length, items); @@ -597,7 +528,7 @@ public ImmutableArray RemoveAt(int index) /// The 0-based index into the array for the element to omit from the returned array. /// The number of elements to remove. /// The new array. - internal ImmutableArray RemoveRange(int index, int length) + public ImmutableArray RemoveRange(int index, int length) { var self = this; self.ThrowNullRefIfNotInitialized(); @@ -615,6 +546,18 @@ internal ImmutableArray RemoveRange(int index, int length) return new ImmutableArray(tmp); } + /// + /// Removes the specified values from this list. + /// + /// The items to remove if matches are found in this list. + /// + /// A new list with the elements removed. + /// + public ImmutableArray RemoveRange(IEnumerable items) + { + return this.RemoveRange(items, EqualityComparer.Default); + } + /// /// Removes the specified values from this list. /// @@ -626,7 +569,7 @@ internal ImmutableArray RemoveRange(int index, int length) /// /// A new list with the elements removed. /// - internal ImmutableArray RemoveRange(IEnumerable items, IEqualityComparer? equalityComparer) + public ImmutableArray RemoveRange(IEnumerable items, IEqualityComparer? equalityComparer) { var self = this; self.ThrowNullRefIfNotInitialized(); @@ -635,73 +578,26 @@ internal ImmutableArray RemoveRange(IEnumerable items, IEqualityComparer(); foreach (var item in items) { - int index = self.IndexOf(item, 0, self.Length, equalityComparer); - while (index >= 0 && indicesToRemove.Add(index) && index + 1 < self.Length) + int index = -1; + do { - // This is a duplicate of one we've found. Try hard to find another instance in the list to remove. index = self.IndexOf(item, index + 1, equalityComparer); - } + } while (index >= 0 && !indicesToRemove.Add(index) && index < self.Length - 1); } return self.RemoveAtRange(indicesToRemove); } - /// - /// Forms a slice out of the current starting at a specified index for a specified length. - /// - /// The index at which to begin this slice. - /// The desired length for the slice. - /// A that consists of length elements from the current starting at start. - public ImmutableArray Slice(int start, int length) - { - var self = this; - self.ThrowNullRefIfNotInitialized(); - return ImmutableArray.Create(self, start, length); - } - /// /// Removes the specified values from this list. /// /// The items to remove if matches are found in this list. - /// - /// The equality comparer to use in the search. - /// /// /// A new list with the elements removed. /// - public ImmutableArray RemoveRange(ReadOnlySpan items, IEqualityComparer? equalityComparer = null) + public ImmutableArray RemoveRange(ImmutableArray items) { - var self = this; - self.ThrowNullRefIfNotInitialized(); - if (items.IsEmpty || self.IsEmpty) - { - return self; - } - - SortedSet? indicesToRemove = null; - foreach (T item in items) - { - int index = self.IndexOf(item, 0, equalityComparer); - if (index < 0) - { - continue; - } - if (indicesToRemove == null) // indicesToRemove is not initialized so current item is valid - { - indicesToRemove = new SortedSet {index}; - } - else if (!indicesToRemove.Add(index)) // If here we have found pre existing index, just skip current 'item'; otherwise current 'item' must be a new item and we can skip checking indicesToRemove.Add() below - { - continue; - } - - while (index < self.Length - 1 && (index = self.IndexOf(item, index + 1, equalityComparer)) >= 0) - { - indicesToRemove.Add(index); - } - } - - return indicesToRemove == null ? self : self.RemoveAtRange(indicesToRemove); + return this.RemoveRange(items, EqualityComparer.Default); } /// @@ -714,50 +610,11 @@ public ImmutableArray RemoveRange(ReadOnlySpan items, IEqualityComparer /// /// A new list with the elements removed. /// - public ImmutableArray RemoveRange(T[] items, IEqualityComparer? equalityComparer = null) + public ImmutableArray RemoveRange(ImmutableArray items, IEqualityComparer? equalityComparer) { - var self = this; - self.ThrowNullRefIfNotInitialized(); - Requires.NotNull(items, nameof(items)); - - if (items.Length == 0 || self.IsEmpty) - { - return self; - } - - List? indicesToRemove = null; - for (int i = 0; i < self.Length; i++) - { - T selfItem = self.array![i]; - bool found = false; - - if (equalityComparer == null || equalityComparer == EqualityComparer.Default) - { - if (Array.IndexOf(items, selfItem) >= 0) - { - found = true; - } - } - else - { - foreach (T item in items) - { - if (equalityComparer.Equals(selfItem, item)) - { - found = true; - break; - } - } - } - - if (found) - { - indicesToRemove ??= new List(); - indicesToRemove.Add(i); - } - } + Requires.NotNull(items.array!, nameof(items)); - return indicesToRemove == null ? self : self.RemoveAtRange(indicesToRemove); + return RemoveRange(items.AsSpan(), equalityComparer); } /// @@ -907,6 +764,47 @@ public IEnumerable OfType() return self.array.OfType(); } + /// + /// Adds the specified values to this list. + /// + /// The values to add. + /// A new list with the elements added. + public ImmutableArray AddRange(ReadOnlySpan items) + { + var self = this; + return self.InsertRange(self.Length, items); + } + + /// + /// Adds the specified values to this list. + /// + /// The values to add. + /// A new list with the elements added. + public ImmutableArray AddRange(params T[] items) + { + var self = this; + return self.InsertRange(self.Length, items); + } + + /// + /// Creates a over the portion of current beginning at a specified position for a specified length. + /// + /// The index at which to begin the span. + /// The number of items in the span. + /// The representation of the + public ReadOnlySpan AsSpan(int start, int length) => new ReadOnlySpan(array, start, length); + +#if !NETSTANDARD2_0 && !NET461 + public ReadOnlySpan AsSpan(Range range) + { + var self = this; + self.ThrowNullRefIfNotInitialized(); + + (int start, int length) = range.GetOffsetAndLength(self.Length); + return new ReadOnlySpan(self.array, start, length); + } +#endif + /// /// Copies the elements of current to an . /// @@ -920,6 +818,142 @@ public void CopyTo(Span destination) self.AsSpan().CopyTo(destination); } + /// + /// Inserts the specified values at the specified index. + /// + /// The index at which to insert the value. + /// The elements to insert. + /// The new immutable collection. + public ImmutableArray InsertRange(int index, T[] items) + { + var self = this; + self.ThrowNullRefIfNotInitialized(); + Requires.Range(index >= 0 && index <= self.Length, nameof(index)); + Requires.NotNull(items, nameof(items)); + + if (items.Length == 0) + { + return self; + } + if (self.Length == 0) + { + return new ImmutableArray(items); + } + + return self.InsertSpanRangeInternal(index, items); + } + + private ImmutableArray InsertSpanRangeInternal(int index, ReadOnlySpan items) + { + var tmp = new T[Length + items.Length]; + if (index != 0) + { + Array.Copy(array!, tmp, index); + } + items.CopyTo(new Span(tmp, index, items.Length)); + if (index != Length) + { + Array.Copy(array!, index, tmp, index + items.Length, Length - index); + } + + return new ImmutableArray(tmp); + } + + /// + /// Inserts the specified values at the specified index. + /// + /// The index at which to insert the value. + /// The elements to insert. + /// The new immutable collection. + public ImmutableArray InsertRange(int index, ReadOnlySpan items) + { + var self = this; + self.ThrowNullRefIfNotInitialized(); + Requires.Range(index >= 0 && index <= self.Length, nameof(index)); + + if (items.IsEmpty) + { + return self; + } + if (self.Length == 0) + { + return items.ToImmutableArray(); + } + + return self.InsertSpanRangeInternal(index, items); + } + + /// + /// Removes the specified values from this list. + /// + /// The items to remove if matches are found in this list. + /// + /// The equality comparer to use in the search. + /// + /// + /// A new list with the elements removed. + /// + public ImmutableArray RemoveRange(ReadOnlySpan items, IEqualityComparer? equalityComparer = null) + { + var self = this; + self.ThrowNullRefIfNotInitialized(); + + if (items.IsEmpty || self.IsEmpty) + { + return self; + } + + if (items.Length == 1) + { + return self.Remove(items[0], equalityComparer); + } + + var indicesToRemove = new SortedSet(); + foreach (var item in items) + { + int index = -1; + do + { + index = self.IndexOf(item, index + 1, equalityComparer); + } while (index >= 0 && !indicesToRemove.Add(index) && index < self.Length - 1); + } + + return self.RemoveAtRange(indicesToRemove); + } + + /// + /// Removes the specified values from this list. + /// + /// The items to remove if matches are found in this list. + /// + /// The equality comparer to use in the search. + /// + /// + /// A new list with the elements removed. + /// + public ImmutableArray RemoveRange(T[] items, IEqualityComparer? equalityComparer = null) + { + var self = this; + self.ThrowNullRefIfNotInitialized(); + + Requires.NotNull(items, nameof(items)); + + return self.RemoveRange(new ReadOnlySpan(items), equalityComparer); + } + + /// + /// Forms a slice out of the current starting at a specified index for a specified length. + /// + /// The index at which to begin this slice. + /// The desired length for the slice. + /// A that consists of length elements from the current starting at start. + public ImmutableArray Slice(int start, int length) + { + var self = this; + self.ThrowNullRefIfNotInitialized(); + return ImmutableArray.Create(self, start, length); + } + #region Explicit interface methods void IList.Insert(int index, T item) @@ -974,7 +1008,7 @@ IImmutableList IImmutableList.AddRange(IEnumerable items) { var self = this; self.ThrowInvalidOperationIfNotInitialized(); - return self.InsertRange(self.Length, items); + return self.AddRange(items); } /// diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayBuilderTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayBuilderTest.cs index 36fe700831de0..5a70cb43e1e31 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayBuilderTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayBuilderTest.cs @@ -66,7 +66,6 @@ public void NormalConstructionRefType() Assert.Equal(7, builder[2].Data); } - /* [Fact] public void AddRangeIEnumerable() { @@ -86,7 +85,6 @@ public void AddRangeIEnumerable() Assert.Equal(Enumerable.Range(1, 4), builder); } - */ [Fact] public void Add() @@ -128,7 +126,6 @@ public void AddRangeBuilder() Assert.Equal(new[] { 1, 2 }, builder1); } - /* [Fact] public void AddRangeImmutableArray() { @@ -153,7 +150,6 @@ public void AddRangeImmutableArray() builder2.AddRange(default(ImmutableArray)); AssertExtensions.Throws("items", () => builder2.AddRange((ImmutableArray.Builder)null)); } - */ [Fact] public void AddRangeDerivedArray() @@ -163,7 +159,6 @@ public void AddRangeDerivedArray() Assert.Equal(new[] { "a", "b" }, builder); } - /* [Fact] public void AddRangeDerivedImmutableArray() { @@ -171,9 +166,7 @@ public void AddRangeDerivedImmutableArray() builder.AddRange(new[] { "a", "b" }.ToImmutableArray()); Assert.Equal(new[] { "a", "b" }, builder); } - */ - /* [Fact] public void AddRangeDerivedBuilder() { @@ -184,7 +177,6 @@ public void AddRangeDerivedBuilder() builderBase.AddRange(builder); Assert.Equal(new[] { "a", "b" }, builderBase); } - */ [Fact] public void Contains() @@ -222,7 +214,7 @@ public void LastIndexOf() public void Insert() { var builder = new ImmutableArray.Builder(); - builder.AddRange(new ReadOnlySpan(new []{1, 2, 3})); + builder.AddRange(1, 2, 3); builder.Insert(1, 4); builder.Insert(4, 5); Assert.Equal(new[] { 1, 4, 2, 3, 5 }, builder); @@ -234,7 +226,7 @@ public void Insert() public void Remove() { var builder = new ImmutableArray.Builder(); - builder.AddRange(new ReadOnlySpan(new[] { 1, 2, 3, 4 })); + builder.AddRange(1, 2, 3, 4); Assert.True(builder.Remove(1)); Assert.False(builder.Remove(6)); Assert.Equal(new[] { 2, 3, 4 }, builder); @@ -250,7 +242,7 @@ public void Remove() public void RemoveAt() { var builder = new ImmutableArray.Builder(); - builder.AddRange(new ReadOnlySpan(new[] { 1, 2, 3, 4 })); + builder.AddRange(1, 2, 3, 4); builder.RemoveAt(0); AssertExtensions.Throws("index", () => builder.RemoveAt(-1)); AssertExtensions.Throws("index", () => builder.RemoveAt(3)); @@ -267,7 +259,7 @@ public void RemoveAt() public void ReverseContents() { var builder = new ImmutableArray.Builder(); - builder.AddRange(new ReadOnlySpan(new[] { 1, 2, 3, 4 })); + builder.AddRange(1, 2, 3, 4); builder.Reverse(); Assert.Equal(new[] { 4, 3, 2, 1 }, builder); @@ -292,7 +284,7 @@ public void ReverseContents() public void Sort() { var builder = new ImmutableArray.Builder(); - builder.AddRange(new ReadOnlySpan(new[] { 2, 4, 1, 3 })); + builder.AddRange(2, 4, 1, 3); builder.Sort(); Assert.Equal(new[] { 1, 2, 3, 4 }, builder); } @@ -305,7 +297,7 @@ public void Sort_Comparison() builder.Sort((x, y) => y.CompareTo(x)); Assert.Equal(Array.Empty(), builder); - builder.AddRange(new ReadOnlySpan(new[] { 2, 4, 1, 3 })); + builder.AddRange(2, 4, 1, 3); builder.Sort((x, y) => y.CompareTo(x)); Assert.Equal(new[] { 4, 3, 2, 1 }, builder); @@ -359,7 +351,7 @@ public void SortOneElementArray() public void SortRange() { var builder = new ImmutableArray.Builder(); - builder.AddRange(new[] { 2, 4, 1, 3 }.AsSpan()); + builder.AddRange(2, 4, 1, 3); AssertExtensions.Throws("index", () => builder.Sort(-1, 2, Comparer.Default)); AssertExtensions.Throws("count", () => builder.Sort(1, 4, Comparer.Default)); AssertExtensions.Throws("count", () => builder.Sort(0, -1, Comparer.Default)); @@ -376,8 +368,8 @@ public void SortComparer() { var builder1 = new ImmutableArray.Builder(); var builder2 = new ImmutableArray.Builder(); - builder1.AddRange(new []{"c", "B", "a"}.AsSpan()); - builder2.AddRange(new []{"c", "B", "a"}.AsSpan()); + builder1.AddRange("c", "B", "a"); + builder2.AddRange("c", "B", "a"); builder1.Sort(StringComparer.OrdinalIgnoreCase); builder2.Sort(StringComparer.Ordinal); Assert.Equal(new[] { "a", "B", "c" }, builder1); @@ -415,7 +407,7 @@ public void Count() public void CountContract() { var builder = new ImmutableArray.Builder(100); - builder.AddRange(Enumerable.Range(1, 100).ToArray().AsSpan()); + builder.AddRange(Enumerable.Range(1, 100)); builder.Count = 10; Assert.Equal(Enumerable.Range(1, 10), builder); builder.Count = 100; @@ -446,7 +438,7 @@ public void IndexSetter() public void ToImmutable() { var builder = new ImmutableArray.Builder(); - builder.AddRange(new []{1, 2, 3}.AsSpan()); + builder.AddRange(1, 2, 3); ImmutableArray array = builder.ToImmutable(); Assert.Equal(1, array[0]); @@ -466,7 +458,7 @@ public void ToImmutable() public void ToImmutableArray() { var builder = new ImmutableArray.Builder(); - builder.AddRange(new []{0, 1, 2}.AsSpan()); + builder.AddRange(0, 1, 2); var array = builder.ToImmutableArray(); Assert.Equal(0, array[0]); @@ -529,7 +521,7 @@ public void Enumerator() Assert.False(enumerator.MoveNext()); var manyElements = new ImmutableArray.Builder(3); - manyElements.AddRange(new []{1, 2, 3}.AsSpan()); + manyElements.AddRange(1, 2, 3); enumerator = manyElements.GetEnumerator(); Assert.True(enumerator.MoveNext()); @@ -550,7 +542,7 @@ public void IEnumerator() Assert.False(enumerator.MoveNext()); var manyElements = new ImmutableArray.Builder(3); - manyElements.AddRange(new []{1, 2, 3}.AsSpan()); + manyElements.AddRange(1, 2, 3); enumerator = ((IEnumerable)manyElements).GetEnumerator(); Assert.True(enumerator.MoveNext()); @@ -740,7 +732,7 @@ public void DebuggerAttributesValid() { DebuggerAttributes.ValidateDebuggerDisplayReferences(ImmutableArray.CreateBuilder()); ImmutableArray.Builder builder = ImmutableArray.CreateBuilder(4); - builder.AddRange(new []{"One", "Two", "Three", "Four"}.AsSpan()); + builder.AddRange("One", "Two", "Three", "Four"); DebuggerAttributeInfo info = DebuggerAttributes.ValidateDebuggerTypeProxyProperties(builder); PropertyInfo itemProperty = info.Properties.Single(pr => pr.GetCustomAttribute().State == DebuggerBrowsableState.RootHidden); string[] items = itemProperty.GetValue(info.Instance) as string[]; diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs index 4ca0bd88a3a09..7349cefa67b5c 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs @@ -1006,7 +1006,6 @@ public void Add(IEnumerable source, IEnumerable items) } } - /* [Theory] [MemberData(nameof(AddData))] public void AddRange(IEnumerable source, IEnumerable items) @@ -1020,7 +1019,6 @@ public void AddRange(IEnumerable source, IEnumerable items) Assert.Equal(source, array); // Make sure the original array wasn't affected. }); } - */ public static IEnumerable AddData() { @@ -1038,7 +1036,6 @@ public static IEnumerable AddData() yield return new object[] { s_oneElement, s_empty }; } - /* [Theory] [MemberData(nameof(Int32EnumerableData))] public void AddRangeInvalid(IEnumerable source) @@ -1053,7 +1050,6 @@ public void AddRangeInvalid(IEnumerable source) TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.AddRange(s_emptyDefault)); TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.AddRange((IEnumerable)s_emptyDefault)); } - */ [Theory] [MemberData(nameof(InsertData))] @@ -1095,7 +1091,6 @@ public void InsertDefaultInvalid(int index) TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.Insert(index, 10)); } - /* [Theory] [MemberData(nameof(Int32EnumerableData))] public void InsertRangeInvalid(IEnumerable source) @@ -1108,19 +1103,17 @@ public void InsertRangeInvalid(IEnumerable source) AssertExtensions.Throws("index", () => array.InsertRange(array.Length + 1, (IEnumerable)s_oneElement)); AssertExtensions.Throws("index", () => array.InsertRange(-1, (IEnumerable)s_oneElement)); } - */ - /* [Theory] [MemberData(nameof(Int32EnumerableData))] public void InsertRangeDefaultInvalid(IEnumerable items) { var array = items.ToImmutableArray(); - + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.InsertRange(1, items)); TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.InsertRange(-1, items)); TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.InsertRange(0, items)); - + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.InsertRange(1, array)); TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.InsertRange(-1, array)); TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.InsertRange(0, array)); @@ -1136,9 +1129,7 @@ public void InsertRangeDefaultInvalid(IEnumerable items) Assert.Throws(() => array.InsertRange(0, (IEnumerable)s_emptyDefault)); } - */ - /* [Theory] [MemberData(nameof(InsertRangeData))] public void InsertRange(IEnumerable source, int index, IEnumerable items) @@ -1162,7 +1153,6 @@ public void InsertRange(IEnumerable source, int index, IEnumerable ite } }); } - */ public static IEnumerable InsertRangeData() { @@ -1269,7 +1259,6 @@ public void RemoveDefaultInvalid() }); } - /* [Theory] [MemberData(nameof(RemoveRangeIndexLengthData))] public void RemoveRangeIndexLength(IEnumerable source, int index, int length) @@ -1278,7 +1267,6 @@ public void RemoveRangeIndexLength(IEnumerable source, int index, int lengt var expected = source.Take(index).Concat(source.Skip(index + length)); Assert.Equal(expected, array.RemoveRange(index, length)); } - */ public static IEnumerable RemoveRangeIndexLengthData() { @@ -1291,7 +1279,6 @@ public static IEnumerable RemoveRangeIndexLengthData() yield return new object[] { new[] { 1, 2, 3, 4 }, 2, 2 }; } - /* [Theory] [MemberData(nameof(Int32EnumerableData))] public void RemoveRangeIndexLengthInvalid(IEnumerable source) @@ -1303,9 +1290,7 @@ public void RemoveRangeIndexLengthInvalid(IEnumerable source) AssertExtensions.Throws("length", () => array.RemoveRange(0, -1)); AssertExtensions.Throws("length", () => array.RemoveRange(0, array.Length + 1)); } - */ - /* [Theory] [InlineData(-1, 0)] [InlineData(0, -1)] @@ -1315,9 +1300,7 @@ public void RemoveRangeIndexLengthDefaultInvalid(int index, int length) { TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(index, length)); } - */ - /* Strange behavior ! [Theory] [MemberData(nameof(RemoveRangeEnumerableData))] public void RemoveRangeEnumerable(IEnumerable source, IEnumerable items, IEqualityComparer comparer) @@ -1327,18 +1310,17 @@ public void RemoveRangeEnumerable(IEnumerable source, IEnumerable item seed: source.ToImmutableArray(), func: (a, i) => a.Remove(i, comparer)); - // Assert.Equal(expected, array.RemoveRange(items, comparer)); // Enumerable overload - // Assert.Equal(expected, array.RemoveRange(items.ToImmutableArray(), comparer)); // Struct overload + Assert.Equal(expected, array.RemoveRange(items, comparer)); // Enumerable overload + Assert.Equal(expected, array.RemoveRange(items.ToImmutableArray(), comparer)); // Struct overload Assert.Equal(expected, ((IImmutableList)array).RemoveRange(items, comparer)); if (comparer == null || comparer == EqualityComparer.Default) { - // Assert.Equal(expected, array.RemoveRange(items)); // Enumerable overload - // Assert.Equal(expected, array.RemoveRange(items.ToImmutableArray())); // Struct overload + Assert.Equal(expected, array.RemoveRange(items)); // Enumerable overload + Assert.Equal(expected, array.RemoveRange(items.ToImmutableArray())); // Struct overload Assert.Equal(expected, ((IImmutableList)array).RemoveRange(items)); } } - */ public static IEnumerable RemoveRangeEnumerableData() { @@ -1375,50 +1357,49 @@ public void RemoveRangeEnumerableInvalid(IEnumerable source) Assert.All(SharedEqualityComparers(), comparer => { // Enumerable overloads, lhs is default - // TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(source)); - // TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(source, comparer)); + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(source)); + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(source, comparer)); Assert.Throws(() => ((IImmutableList)s_emptyDefault).RemoveRange(source)); Assert.Throws(() => ((IImmutableList)s_emptyDefault).RemoveRange(source, comparer)); // Struct overloads, lhs is default - // TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(array)); - // TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(array, comparer)); + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(array)); + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(array, comparer)); // Struct overloads, rhs is default - // AssertExtensions.Throws("items", () => array.RemoveRange(s_emptyDefault)); - // AssertExtensions.Throws("items", () => array.RemoveRange(s_emptyDefault, comparer)); + AssertExtensions.Throws("items", () => array.RemoveRange(s_emptyDefault)); + AssertExtensions.Throws("items", () => array.RemoveRange(s_emptyDefault, comparer)); // Enumerable overloads, rhs is default - // Assert.Throws(() => array.RemoveRange((IEnumerable)s_emptyDefault)); - // Assert.Throws(() => array.RemoveRange((IEnumerable)s_emptyDefault, comparer)); + Assert.Throws(() => array.RemoveRange((IEnumerable)s_emptyDefault)); + Assert.Throws(() => array.RemoveRange((IEnumerable)s_emptyDefault, comparer)); Assert.Throws(() => ((IImmutableList)array).RemoveRange(s_emptyDefault)); Assert.Throws(() => ((IImmutableList)array).RemoveRange(s_emptyDefault, comparer)); // Struct overloads, both sides are default - // AssertExtensions.Throws("items", () => s_emptyDefault.RemoveRange(s_emptyDefault)); - // AssertExtensions.Throws("items", () => s_emptyDefault.RemoveRange(s_emptyDefault, comparer)); + AssertExtensions.Throws("items", () => s_emptyDefault.RemoveRange(s_emptyDefault)); + AssertExtensions.Throws("items", () => s_emptyDefault.RemoveRange(s_emptyDefault, comparer)); // Enumerable overloads, both sides are default - // TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange((IEnumerable)s_emptyDefault)); - // TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange((IEnumerable)s_emptyDefault, comparer)); + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange((IEnumerable)s_emptyDefault)); + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange((IEnumerable)s_emptyDefault, comparer)); Assert.Throws(() => ((IImmutableList)s_emptyDefault).RemoveRange(s_emptyDefault)); Assert.Throws(() => ((IImmutableList)s_emptyDefault).RemoveRange(s_emptyDefault, comparer)); // Enumerable overloads, rhs is null - // AssertExtensions.Throws("items", () => array.RemoveRange(items: null)); - // AssertExtensions.Throws("items", () => array.RemoveRange(items: null, equalityComparer: comparer)); + AssertExtensions.Throws("items", () => array.RemoveRange(items: null)); + AssertExtensions.Throws("items", () => array.RemoveRange(items: null, equalityComparer: comparer)); AssertExtensions.Throws("items", () => ((IImmutableList)array).RemoveRange(items: null)); AssertExtensions.Throws("items", () => ((IImmutableList)array).RemoveRange(items: null, equalityComparer: comparer)); // Enumerable overloads, lhs is default and rhs is null - // TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(items: null)); - // TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(items: null, equalityComparer: comparer)); + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(items: null)); + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(items: null, equalityComparer: comparer)); Assert.Throws(() => ((IImmutableList)s_emptyDefault).RemoveRange(items: null)); Assert.Throws(() => ((IImmutableList)s_emptyDefault).RemoveRange(items: null, equalityComparer: comparer)); }); } - /* [Fact] public void RemoveRangeEnumerableRegression() { @@ -1435,7 +1416,6 @@ public void RemoveRangeEnumerableRegression() Assert.Equal(oneElementBoxed, oneElementBoxed); } - */ [Theory] [MemberData(nameof(RemoveAllData))] From 4f1ef20081580f36ed9d1ad4b874640dc944c91b Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Tue, 30 Nov 2021 17:45:22 +0800 Subject: [PATCH 05/29] Try to use _OR_GREATER pre-compilation symbol. --- .../ref/System.Collections.Immutable.cs | 2 +- .../src/System/Collections/Immutable/ImmutableArray_1.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs b/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs index 7e76b1b5a6193..16ccd8fe9c551 100644 --- a/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs +++ b/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs @@ -127,7 +127,7 @@ public static partial class ImmutableArray public System.ReadOnlyMemory AsMemory() { throw null; } public System.ReadOnlySpan AsSpan() { throw null; } public System.ReadOnlySpan AsSpan(int start, int length) { throw null; } -#if !NETSTANDARD2_0 && !NET461 +#if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER public System.ReadOnlySpan AsSpan(System.Range range) { throw null; } #endif public System.Collections.Immutable.ImmutableArray< diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs index bba5e8116cbf2..3188abb9bdd3a 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs @@ -794,7 +794,7 @@ public ImmutableArray AddRange(params T[] items) /// The representation of the public ReadOnlySpan AsSpan(int start, int length) => new ReadOnlySpan(array, start, length); -#if !NETSTANDARD2_0 && !NET461 +#if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER public ReadOnlySpan AsSpan(Range range) { var self = this; From 6691eda412f9841e2174006d979012d0890dc03d Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Thu, 2 Dec 2021 11:24:58 +0800 Subject: [PATCH 06/29] issue-22928 Test cases of ImmutableArray.Create() overloads. --- .../Collections/Immutable/ImmutableArray.cs | 8 +++++-- .../tests/ImmutableArrayTest.cs | 24 ++++++++++++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray.cs index b59478ce6dcab..b9baf66aa1d2d 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray.cs @@ -89,6 +89,11 @@ public static ImmutableArray Create(T item1, T item2, T item3, T item4) /// An immutable array. public static ImmutableArray Create(ReadOnlySpan items) { + if (items.Length == 0) + { + return ImmutableArray.Empty; + } + T[] array = items.ToArray(); return new ImmutableArray(array); } @@ -101,8 +106,7 @@ public static ImmutableArray Create(ReadOnlySpan items) /// An immutable array. public static ImmutableArray Create(Span items) { - T[] array = items.ToArray(); - return new ImmutableArray(array); + return Create((ReadOnlySpan)items); } /// diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs index 7349cefa67b5c..1bfa92e2cb374 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs @@ -155,7 +155,7 @@ public void AsMemoryRoundTripDefaultArrayStringTests() [Fact] public void CreateEnumerableElementType() { - // Create should not have the same semantics as CreateRange, except for arrays. + // Create should not have the same semantics as CreateRange, except for arrays, span and readonlySpan. // If you pass in an IEnumerable to Create, you should get an // ImmutableArray>. However, if you pass a T[] in, you should get // a ImmutableArray. @@ -168,6 +168,12 @@ public void CreateEnumerableElementType() var enumerable = Enumerable.Empty(); Assert.IsType>>(ImmutableArray.Create(enumerable)); + + Span span = Span.Empty; + Assert.IsType>(ImmutableArray.Create(span)); + + ReadOnlySpan readonlySpan = ReadOnlySpan.Empty; + Assert.IsType>(ImmutableArray.Create(readonlySpan)); } [Fact] @@ -175,6 +181,8 @@ public void CreateEmpty() { Assert.True(s_empty == ImmutableArray.Create()); Assert.True(s_empty == ImmutableArray.Create(new int[0])); + Assert.True(s_empty == ImmutableArray.Create(Span.Empty)); + Assert.True(s_empty == ImmutableArray.Create(ReadOnlySpan.Empty)); } [Theory] @@ -424,6 +432,20 @@ public void CreateFromArray(IEnumerable source) Assert.Equal(source, ImmutableArray.Create(source.ToArray())); } + [Theory] + [MemberData(nameof(Int32EnumerableData))] + public void CreateFromSpan(IEnumerable source) + { + Assert.Equal(source, ImmutableArray.Create(source.ToArray().AsSpan())); + } + + [Theory] + [MemberData(nameof(Int32EnumerableData))] + public void CreateFromReadOnlySpan(IEnumerable source) + { + Assert.Equal(source, ImmutableArray.Create(new ReadOnlySpan(source.ToArray()))); + } + [Fact] public void CreateFromArrayNull() { From b46c0af0dde9f26aa3d513aa305b644aa304be92 Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Thu, 2 Dec 2021 17:57:20 +0800 Subject: [PATCH 07/29] issue-22928 Add test cases for ImmutableArray.ToImmutableArray(), ImmutableArray.AddRange(). --- .../tests/ImmutableArrayTest.cs | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs index 1bfa92e2cb374..2a5f78397fc7a 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs @@ -608,7 +608,12 @@ public void CastArrayBadReference() public void ToImmutableArray(IEnumerable source) { var array = source.ToImmutableArray(); + Span span = source.ToArray().AsSpan(); + Assert.Equal(source, array); + Assert.Equal(source, span.ToImmutableArray()); + Assert.Equal(source, ((ReadOnlySpan)span).ToImmutableArray()); + Assert.True(array == array.ToImmutableArray()); } @@ -1037,11 +1042,27 @@ public void AddRange(IEnumerable source, IEnumerable items) var array = source.ToImmutableArray(); Assert.Equal(source.Concat(items), array.AddRange(it)); // Enumerable overload - Assert.Equal(source.Concat(items), array.AddRange(it.ToImmutableArray())); // Struct overload + Assert.Equal(source.Concat(items), array.AddRange(it.ToImmutableArray())); // ImmutableArray overload + + int[] itArray = it.ToArray(); + Assert.Equal(source.Concat(items), array.AddRange(itArray)); // Array overload + Assert.Equal(source.Concat(items), array.AddRange(new ReadOnlySpan(itArray))); // ReadOnlySpan overload + Assert.Equal(source, array); // Make sure the original array wasn't affected. }); } + [Theory] + [MemberData(nameof(Int32EnumerableData))] + public void AddRangeEmptyOptimization(IEnumerable source) + { + ImmutableArray array = source.ToImmutableArray(); + + // Verify that underlying array is reference-equal as original array + Assert.True(array.AddRange(Array.Empty()) == array); + Assert.True(array.AddRange(ReadOnlySpan.Empty) == array); + } + public static IEnumerable AddData() { yield return new object[] { new int[] { }, new[] { 1 } }; @@ -1065,8 +1086,13 @@ public void AddRangeInvalid(IEnumerable source) // If the lhs or the rhs is a default ImmutableArray, AddRange should throw. TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.AddRange(source)); // Enumerable overload - TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.AddRange(source.ToImmutableArray())); // Struct overload - TestExtensionsMethods.ValidateDefaultThisBehavior(() => source.ToImmutableArray().AddRange(s_emptyDefault)); // Struct overload + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.AddRange(source.ToImmutableArray())); // ImmutableArray overload + TestExtensionsMethods.ValidateDefaultThisBehavior(() => source.ToImmutableArray().AddRange(s_emptyDefault)); // ImmutableArray overload + + int[] sourceArray = source.ToArray(); + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.AddRange(sourceArray)); // Array overload + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.AddRange(new ReadOnlySpan(sourceArray))); // ReadOnlySpan overload + Assert.Throws(() => source.ToImmutableArray().AddRange((IEnumerable)s_emptyDefault)); // Enumerable overload TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.AddRange(s_emptyDefault)); From 314436625ac697bbc555c70ce1b9ad5d9ce2e212 Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Fri, 3 Dec 2021 12:31:34 +0800 Subject: [PATCH 08/29] Test cases for AsSpan(start, length). --- .../tests/ImmutableArrayTest.cs | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs index 2a5f78397fc7a..8e6dde960fe98 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs @@ -68,19 +68,29 @@ public void AsSpanRoundTripTests(IEnumerable source) public void AsSpanRoundTripEmptyArrayTests() { ImmutableArray immutableArray = ImmutableArray.Create(Array.Empty()); + ReadOnlySpan span = immutableArray.AsSpan(); Assert.Equal(immutableArray, span.ToArray()); Assert.Equal(immutableArray.Length, span.Length); + + ReadOnlySpan rangedSpan = immutableArray.AsSpan(0, 0); + Assert.Equal(immutableArray, rangedSpan.ToArray()); + Assert.Equal(immutableArray.Length, rangedSpan.Length); } [Fact] public void AsSpanRoundTripDefaultArrayTests() { ImmutableArray immutableArray = new ImmutableArray(); - ReadOnlySpan span = immutableArray.AsSpan(); Assert.True(immutableArray.IsDefault); + + ReadOnlySpan span = immutableArray.AsSpan(); Assert.Equal(0, span.Length); Assert.True(span.IsEmpty); + + ReadOnlySpan rangeSpan = immutableArray.AsSpan(0, 0); + Assert.Equal(0, rangeSpan.Length); + Assert.True(rangeSpan.IsEmpty); } [Theory] @@ -97,10 +107,15 @@ public void AsSpanRoundTripStringTests(IEnumerable source) public void AsSpanRoundTripDefaultArrayStringTests() { ImmutableArray immutableArray = new ImmutableArray(); - ReadOnlySpan span = immutableArray.AsSpan(); Assert.True(immutableArray.IsDefault); + + ReadOnlySpan span = immutableArray.AsSpan(); Assert.Equal(0, span.Length); Assert.True(span.IsEmpty); + + ReadOnlySpan rangeSpan = immutableArray.AsSpan(0, 0); + Assert.Equal(0, rangeSpan.Length); + Assert.True(rangeSpan.IsEmpty); } [Theory] @@ -1308,7 +1323,7 @@ public void RemoveDefaultInvalid() } [Theory] - [MemberData(nameof(RemoveRangeIndexLengthData))] + [MemberData(nameof(RangeIndexLengthData))] public void RemoveRangeIndexLength(IEnumerable source, int index, int length) { var array = source.ToImmutableArray(); @@ -1316,7 +1331,28 @@ public void RemoveRangeIndexLength(IEnumerable source, int index, int lengt Assert.Equal(expected, array.RemoveRange(index, length)); } - public static IEnumerable RemoveRangeIndexLengthData() + [Theory] + [MemberData(nameof(RangeIndexLengthData))] + public void AsSpanStartLength(IEnumerable source, int start, int length) + { + var array = source.ToImmutableArray(); + var expected = source.Skip(start).Take(length); + Assert.Equal(expected, array.AsSpan(start, length).ToArray()); + } + + [Theory] + [MemberData(nameof(Int32EnumerableData))] + public void AsSpanStartLengthInvalid(IEnumerable source) + { + var array = source.ToImmutableArray(); + + AssertExtensions.Throws(() => array.AsSpan(-1, 1)); + AssertExtensions.Throws(() => array.AsSpan(array.Length + 1, 1)); + AssertExtensions.Throws(() => array.AsSpan(0, -1)); + AssertExtensions.Throws(() => array.AsSpan(0, array.Length + 1)); + } + + public static IEnumerable RangeIndexLengthData() { yield return new object[] { s_empty, 0, 0 }; yield return new object[] { s_oneElement, 1, 0 }; From d0571c40ddd4f1f6d8afa4ec4b9c160b150a6f40 Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Fri, 3 Dec 2021 15:26:34 +0800 Subject: [PATCH 09/29] Test cases of AsSpan(Range). --- .../tests/ImmutableArrayTest.cs | 39 +++++++++++++++---- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs index 8e6dde960fe98..ddc434d2d1a09 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs @@ -73,9 +73,15 @@ public void AsSpanRoundTripEmptyArrayTests() Assert.Equal(immutableArray, span.ToArray()); Assert.Equal(immutableArray.Length, span.Length); - ReadOnlySpan rangedSpan = immutableArray.AsSpan(0, 0); + ReadOnlySpan startRangedSpan = immutableArray.AsSpan(0, 0); + Assert.Equal(immutableArray, startRangedSpan.ToArray()); + Assert.Equal(immutableArray.Length, startRangedSpan.Length); + +#if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + ReadOnlySpan rangedSpan = immutableArray.AsSpan(new Range(0, 0)); Assert.Equal(immutableArray, rangedSpan.ToArray()); Assert.Equal(immutableArray.Length, rangedSpan.Length); +#endif } [Fact] @@ -88,9 +94,9 @@ public void AsSpanRoundTripDefaultArrayTests() Assert.Equal(0, span.Length); Assert.True(span.IsEmpty); - ReadOnlySpan rangeSpan = immutableArray.AsSpan(0, 0); - Assert.Equal(0, rangeSpan.Length); - Assert.True(rangeSpan.IsEmpty); + ReadOnlySpan startRangeSpan = immutableArray.AsSpan(0, 0); + Assert.Equal(0, startRangeSpan.Length); + Assert.True(startRangeSpan.IsEmpty); } [Theory] @@ -113,11 +119,19 @@ public void AsSpanRoundTripDefaultArrayStringTests() Assert.Equal(0, span.Length); Assert.True(span.IsEmpty); - ReadOnlySpan rangeSpan = immutableArray.AsSpan(0, 0); - Assert.Equal(0, rangeSpan.Length); - Assert.True(rangeSpan.IsEmpty); + ReadOnlySpan startRangeSpan = immutableArray.AsSpan(0, 0); + Assert.Equal(0, startRangeSpan.Length); + Assert.True(startRangeSpan.IsEmpty); } +#if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + [Fact] + public void AsSpanEmptyRangeNotInitialized() + { + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.AsSpan(new Range(0, 0))); + } +#endif + [Theory] [MemberData(nameof(Int32EnumerableData))] public void AsMemoryRoundTripTests(IEnumerable source) @@ -1338,6 +1352,10 @@ public void AsSpanStartLength(IEnumerable source, int start, int length) var array = source.ToImmutableArray(); var expected = source.Skip(start).Take(length); Assert.Equal(expected, array.AsSpan(start, length).ToArray()); + +#if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + Assert.Equal(expected, array.AsSpan(new Range(start, start + length)).ToArray()); +#endif } [Theory] @@ -1350,6 +1368,13 @@ public void AsSpanStartLengthInvalid(IEnumerable source) AssertExtensions.Throws(() => array.AsSpan(array.Length + 1, 1)); AssertExtensions.Throws(() => array.AsSpan(0, -1)); AssertExtensions.Throws(() => array.AsSpan(0, array.Length + 1)); + +#if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + AssertExtensions.Throws(() => array.AsSpan(new Range(-1, 0))); + AssertExtensions.Throws(() => array.AsSpan(new Range(array.Length + 1, array.Length + 2))); + AssertExtensions.Throws(() => array.AsSpan(new Range(0, -1))); + AssertExtensions.Throws(() => array.AsSpan(new Range(0, array.Length + 1))); +#endif } public static IEnumerable RangeIndexLengthData() From 1516ed0d61445707bdf4190e894c781186eb1567 Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Fri, 3 Dec 2021 16:29:07 +0800 Subject: [PATCH 10/29] Test cases of ImmutableArray.CopyTo(). --- .../System/Collections/Immutable/ImmutableArray_1.cs | 2 +- .../tests/ImmutableArrayTest.cs | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs index 3188abb9bdd3a..c68e45d265e3e 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs @@ -812,7 +812,7 @@ public ReadOnlySpan AsSpan(Range range) public void CopyTo(Span destination) { var self = this; - self.ThrowInvalidOperationIfNotInitialized(); + self.ThrowNullRefIfNotInitialized(); Requires.Range(self.Length <= destination.Length, nameof(destination)); self.AsSpan().CopyTo(destination); diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs index ddc434d2d1a09..6d1f1d73888e0 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs @@ -1706,6 +1706,15 @@ public void CopyTo(IEnumerable source, int sourceIndex, IEnumerable de Assert.Equal(destination.Skip(destinationIndex + array.Length), destinationArray.Skip(destinationIndex + array.Length)); }); + CopyAndInvoke(destination, destinationArray => + { + array.CopyTo(new Span(destinationArray, destinationIndex, array.Length)); + + Assert.Equal(destination.Take(destinationIndex), destinationArray.Take(destinationIndex)); + Assert.Equal(source, destinationArray.Skip(destinationIndex).Take(array.Length)); + Assert.Equal(destination.Skip(destinationIndex + array.Length), destinationArray.Skip(destinationIndex + array.Length)); + }); + if (destinationIndex == 0) { CopyAndInvoke(destination, destinationArray => @@ -1753,6 +1762,7 @@ public void CopyToInvalid(IEnumerable source) if (array.Length > 0) { AssertExtensions.Throws("destinationArray", string.Empty, () => array.CopyTo(array.Length - 1, new int[1], 1, 1)); // Not enough room in the destination. + AssertExtensions.Throws("destination", () => array.CopyTo(new Span(new int[array.Length - 1]))); // Not enough room in the destination. } } @@ -1772,6 +1782,8 @@ public void CopyToDefaultInvalid(int destinationLength, int destinationIndex) TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.CopyTo(destination, destinationIndex)); TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.CopyTo(0, destination, destinationIndex, 0)); + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.CopyTo(new Span(destination, destinationIndex, destinationLength - destinationIndex))); + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.CopyTo(new Span(destination, destinationIndex, 0))); } [Theory] From c8e2b68c7c2850e1752edccab3d282d10382608d Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Mon, 6 Dec 2021 17:47:16 +0800 Subject: [PATCH 11/29] Test cases of InsertRange(). --- .../tests/ImmutableArrayTest.cs | 71 ++++++++++++++----- .../tests/TestExtensionsMethods.cs | 18 +++++ 2 files changed, 71 insertions(+), 18 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs index 6d1f1d73888e0..5515d2dcf71e5 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs @@ -1172,46 +1172,64 @@ public void InsertDefaultInvalid(int index) [MemberData(nameof(Int32EnumerableData))] public void InsertRangeInvalid(IEnumerable source) { - var array = source.ToImmutableArray(); + var immutableArray = source.ToImmutableArray(); + + AssertExtensions.Throws("index", () => immutableArray.InsertRange(immutableArray.Length + 1, s_oneElement)); + AssertExtensions.Throws("index", () => immutableArray.InsertRange(-1, s_oneElement)); - AssertExtensions.Throws("index", () => array.InsertRange(array.Length + 1, s_oneElement)); - AssertExtensions.Throws("index", () => array.InsertRange(-1, s_oneElement)); + AssertExtensions.Throws("index", () => immutableArray.InsertRange(immutableArray.Length + 1, (IEnumerable)s_oneElement)); + AssertExtensions.Throws("index", () => immutableArray.InsertRange(-1, (IEnumerable)s_oneElement)); - AssertExtensions.Throws("index", () => array.InsertRange(array.Length + 1, (IEnumerable)s_oneElement)); - AssertExtensions.Throws("index", () => array.InsertRange(-1, (IEnumerable)s_oneElement)); + int[] array = s_oneElement.ToArray(); + AssertExtensions.Throws("index", () => immutableArray.InsertRange(immutableArray.Length + 1, array)); + AssertExtensions.Throws("index", () => immutableArray.InsertRange(-1, array)); + + var span = new ReadOnlySpan(array); + AssertExtensions.Throws("index", span, s => immutableArray.InsertRange(immutableArray.Length + 1, s)); + AssertExtensions.Throws("index", span, s => immutableArray.InsertRange(-1, s)); } [Theory] [MemberData(nameof(Int32EnumerableData))] public void InsertRangeDefaultInvalid(IEnumerable items) { - var array = items.ToImmutableArray(); + var immutableArray = items.ToImmutableArray(); TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.InsertRange(1, items)); TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.InsertRange(-1, items)); TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.InsertRange(0, items)); + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.InsertRange(1, immutableArray)); + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.InsertRange(-1, immutableArray)); + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.InsertRange(0, immutableArray)); + + int[] array = items.ToArray(); TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.InsertRange(1, array)); TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.InsertRange(-1, array)); TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.InsertRange(0, array)); - TestExtensionsMethods.ValidateDefaultThisBehavior(() => array.InsertRange(1, s_emptyDefault)); - TestExtensionsMethods.ValidateDefaultThisBehavior(() => array.InsertRange(-1, s_emptyDefault)); - TestExtensionsMethods.ValidateDefaultThisBehavior(() => array.InsertRange(0, s_emptyDefault)); + var span = new ReadOnlySpan(array); + TestExtensionsMethods.ValidateDefaultThisBehavior(span, s => s_emptyDefault.InsertRange(1, s)); + TestExtensionsMethods.ValidateDefaultThisBehavior(span, s => s_emptyDefault.InsertRange(-1, s)); + TestExtensionsMethods.ValidateDefaultThisBehavior(span, s => s_emptyDefault.InsertRange(0, s)); - if (array.Length > 0) + TestExtensionsMethods.ValidateDefaultThisBehavior(() => immutableArray.InsertRange(1, s_emptyDefault)); + TestExtensionsMethods.ValidateDefaultThisBehavior(() => immutableArray.InsertRange(-1, s_emptyDefault)); + TestExtensionsMethods.ValidateDefaultThisBehavior(() => immutableArray.InsertRange(0, s_emptyDefault)); + + if (immutableArray.Length > 0) { - Assert.Throws(() => array.InsertRange(1, (IEnumerable)s_emptyDefault)); + Assert.Throws(() => immutableArray.InsertRange(1, (IEnumerable)s_emptyDefault)); } - Assert.Throws(() => array.InsertRange(0, (IEnumerable)s_emptyDefault)); + Assert.Throws(() => immutableArray.InsertRange(0, (IEnumerable)s_emptyDefault)); } [Theory] [MemberData(nameof(InsertRangeData))] public void InsertRange(IEnumerable source, int index, IEnumerable items) { - var array = source.ToImmutableArray(); + var immutableArray = source.ToImmutableArray(); Assert.All(ChangeType(items), it => { @@ -1219,14 +1237,31 @@ public void InsertRange(IEnumerable source, int index, IEnumerable ite .Concat(items) .Concat(source.Skip(index)); - Assert.Equal(expected, array.InsertRange(index, it)); // Enumerable overload - Assert.Equal(expected, array.InsertRange(index, it.ToImmutableArray())); // Struct overload + Assert.Equal(expected, immutableArray.InsertRange(index, it)); // Enumerable overload + Assert.Equal(expected, immutableArray.InsertRange(index, it.ToImmutableArray())); // ImmutableArray overload + + int[] array; + if (items.GetType() == typeof(uint[])) + { + array = it.Select(i => (int)i).ToArray(); + } + else + { + array = it.ToArray(); + } + + Assert.Equal(expected, immutableArray.InsertRange(index, array)); // Array overload + Assert.Equal(expected, immutableArray.InsertRange(index, new ReadOnlySpan(array))); // Span overload - if (index == array.Length) + if (index == immutableArray.Length) { // Insertion at the end is equivalent to adding. - Assert.Equal(expected, array.InsertRange(index, it)); // Enumerable overload - Assert.Equal(expected, array.InsertRange(index, it.ToImmutableArray())); // Struct overload + expected = source.Concat(items); + + Assert.Equal(expected, immutableArray.InsertRange(index, it)); // Enumerable overload + Assert.Equal(expected, immutableArray.InsertRange(index, it.ToImmutableArray())); // Struct overload + Assert.Equal(expected, immutableArray.InsertRange(index, array)); // Array overload + Assert.Equal(expected, immutableArray.InsertRange(index, new ReadOnlySpan(array))); // Span overload } }); } diff --git a/src/libraries/System.Collections.Immutable/tests/TestExtensionsMethods.cs b/src/libraries/System.Collections.Immutable/tests/TestExtensionsMethods.cs index 7373a24d5d778..cd3138e1a3442 100644 --- a/src/libraries/System.Collections.Immutable/tests/TestExtensionsMethods.cs +++ b/src/libraries/System.Collections.Immutable/tests/TestExtensionsMethods.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using Xunit; +using Xunit.Sdk; namespace System.Collections.Immutable.Tests { @@ -14,5 +15,22 @@ internal static void ValidateDefaultThisBehavior(Action a) { Assert.Throws(a); } + + internal static void ValidateDefaultThisBehavior(ReadOnlySpan span, AssertExtensions.AssertThrowsActionReadOnly action) + { + try + { + action(span); + throw new ThrowsException(typeof(NullReferenceException)); + } + catch (NullReferenceException nullRefEx) when (nullRefEx.GetType() == typeof(NullReferenceException)) + { + return; + } + catch (Exception ex) + { + throw new ThrowsException(typeof(NullReferenceException), ex); + } + } } } From 7fd7041db2d86aa167a519b0275fcc13cd671873 Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Wed, 8 Dec 2021 14:19:24 +0800 Subject: [PATCH 12/29] Test cases of RemoveRange(). --- .../tests/ImmutableArrayTest.cs | 72 ++++++++++++------- 1 file changed, 47 insertions(+), 25 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs index 5515d2dcf71e5..ee05a0491859d 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs @@ -1259,7 +1259,7 @@ public void InsertRange(IEnumerable source, int index, IEnumerable ite expected = source.Concat(items); Assert.Equal(expected, immutableArray.InsertRange(index, it)); // Enumerable overload - Assert.Equal(expected, immutableArray.InsertRange(index, it.ToImmutableArray())); // Struct overload + Assert.Equal(expected, immutableArray.InsertRange(index, it.ToImmutableArray())); // ImmutableArray overload Assert.Equal(expected, immutableArray.InsertRange(index, array)); // Array overload Assert.Equal(expected, immutableArray.InsertRange(index, new ReadOnlySpan(array))); // Span overload } @@ -1449,20 +1449,28 @@ public void RemoveRangeIndexLengthDefaultInvalid(int index, int length) [MemberData(nameof(RemoveRangeEnumerableData))] public void RemoveRangeEnumerable(IEnumerable source, IEnumerable items, IEqualityComparer comparer) { - var array = source.ToImmutableArray(); + ImmutableArray immutableArray = source.ToImmutableArray(); IEnumerable expected = items.Aggregate( seed: source.ToImmutableArray(), func: (a, i) => a.Remove(i, comparer)); - Assert.Equal(expected, array.RemoveRange(items, comparer)); // Enumerable overload - Assert.Equal(expected, array.RemoveRange(items.ToImmutableArray(), comparer)); // Struct overload - Assert.Equal(expected, ((IImmutableList)array).RemoveRange(items, comparer)); + Assert.Equal(expected, immutableArray.RemoveRange(items, comparer)); // Enumerable overload + Assert.Equal(expected, immutableArray.RemoveRange(items.ToImmutableArray(), comparer)); // ImmutableArray overload + + int[] array = items.ToArray(); + Assert.Equal(expected, immutableArray.RemoveRange(array, comparer)); // Array overload + ReadOnlySpan span = new ReadOnlySpan(array); + Assert.Equal(expected, immutableArray.RemoveRange(span, comparer)); // Span overload + + Assert.Equal(expected, ((IImmutableList)immutableArray).RemoveRange(items, comparer)); if (comparer == null || comparer == EqualityComparer.Default) { - Assert.Equal(expected, array.RemoveRange(items)); // Enumerable overload - Assert.Equal(expected, array.RemoveRange(items.ToImmutableArray())); // Struct overload - Assert.Equal(expected, ((IImmutableList)array).RemoveRange(items)); + Assert.Equal(expected, immutableArray.RemoveRange(items)); // Enumerable overload + Assert.Equal(expected, immutableArray.RemoveRange(items.ToImmutableArray())); // ImmutableArray overload + Assert.Equal(expected, immutableArray.RemoveRange(array)); // Array overload + Assert.Equal(expected, immutableArray.RemoveRange(span)); // Span overload + Assert.Equal(expected, ((IImmutableList)immutableArray).RemoveRange(items)); } } @@ -1496,7 +1504,7 @@ public static IEnumerable RemoveRangeEnumerableData() [MemberData(nameof(Int32EnumerableData))] public void RemoveRangeEnumerableInvalid(IEnumerable source) { - var array = source.ToImmutableArray(); + var immutableArray = source.ToImmutableArray(); Assert.All(SharedEqualityComparers(), comparer => { @@ -1507,18 +1515,18 @@ public void RemoveRangeEnumerableInvalid(IEnumerable source) Assert.Throws(() => ((IImmutableList)s_emptyDefault).RemoveRange(source, comparer)); // Struct overloads, lhs is default - TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(array)); - TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(array, comparer)); + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(immutableArray)); + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(immutableArray, comparer)); // Struct overloads, rhs is default - AssertExtensions.Throws("items", () => array.RemoveRange(s_emptyDefault)); - AssertExtensions.Throws("items", () => array.RemoveRange(s_emptyDefault, comparer)); + AssertExtensions.Throws("items", () => immutableArray.RemoveRange(s_emptyDefault)); + AssertExtensions.Throws("items", () => immutableArray.RemoveRange(s_emptyDefault, comparer)); // Enumerable overloads, rhs is default - Assert.Throws(() => array.RemoveRange((IEnumerable)s_emptyDefault)); - Assert.Throws(() => array.RemoveRange((IEnumerable)s_emptyDefault, comparer)); - Assert.Throws(() => ((IImmutableList)array).RemoveRange(s_emptyDefault)); - Assert.Throws(() => ((IImmutableList)array).RemoveRange(s_emptyDefault, comparer)); + Assert.Throws(() => immutableArray.RemoveRange((IEnumerable)s_emptyDefault)); + Assert.Throws(() => immutableArray.RemoveRange((IEnumerable)s_emptyDefault, comparer)); + Assert.Throws(() => ((IImmutableList)immutableArray).RemoveRange(s_emptyDefault)); + Assert.Throws(() => ((IImmutableList)immutableArray).RemoveRange(s_emptyDefault, comparer)); // Struct overloads, both sides are default AssertExtensions.Throws("items", () => s_emptyDefault.RemoveRange(s_emptyDefault)); @@ -1531,16 +1539,30 @@ public void RemoveRangeEnumerableInvalid(IEnumerable source) Assert.Throws(() => ((IImmutableList)s_emptyDefault).RemoveRange(s_emptyDefault, comparer)); // Enumerable overloads, rhs is null - AssertExtensions.Throws("items", () => array.RemoveRange(items: null)); - AssertExtensions.Throws("items", () => array.RemoveRange(items: null, equalityComparer: comparer)); - AssertExtensions.Throws("items", () => ((IImmutableList)array).RemoveRange(items: null)); - AssertExtensions.Throws("items", () => ((IImmutableList)array).RemoveRange(items: null, equalityComparer: comparer)); + AssertExtensions.Throws("items", () => immutableArray.RemoveRange(items: null as IEnumerable)); + AssertExtensions.Throws("items", () => immutableArray.RemoveRange(items: null as IEnumerable, equalityComparer: comparer)); + AssertExtensions.Throws("items", () => ((IImmutableList)immutableArray).RemoveRange(items: null)); + AssertExtensions.Throws("items", () => ((IImmutableList)immutableArray).RemoveRange(items: null, equalityComparer: comparer)); // Enumerable overloads, lhs is default and rhs is null - TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(items: null)); - TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(items: null, equalityComparer: comparer)); - Assert.Throws(() => ((IImmutableList)s_emptyDefault).RemoveRange(items: null)); - Assert.Throws(() => ((IImmutableList)s_emptyDefault).RemoveRange(items: null, equalityComparer: comparer)); + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(items: null as IEnumerable)); + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(items: null as IEnumerable, equalityComparer: comparer)); + Assert.Throws(() => ((IImmutableList)s_emptyDefault).RemoveRange(items: null as IEnumerable)); + Assert.Throws(() => ((IImmutableList)s_emptyDefault).RemoveRange(items: null as IEnumerable, equalityComparer: comparer)); + + // Array overloads, lhs is default and rhs is null + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(items: null as int[], comparer)); + + // Array overloads, rhs is null + AssertExtensions.Throws("items", () => immutableArray.RemoveRange(items: null as int[], equalityComparer: comparer)); + + // Array overloads, lhs is default + int[] array = source.ToArray(); + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(items: array, comparer)); + + // Span overloads, lhs is default + var span = new ReadOnlySpan(array); + TestExtensionsMethods.ValidateDefaultThisBehavior(span, s => s_emptyDefault.RemoveRange(items: s, comparer)); }); } From 1f67b98955bbf1b498709fc5ff0450cd939792ee Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Mon, 13 Dec 2021 18:09:46 +0800 Subject: [PATCH 13/29] Add test cases of .Slice(). --- .../tests/ImmutableArrayTest.cs | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs index ee05a0491859d..6a331e988c1fd 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs @@ -379,6 +379,14 @@ public void CreateFromSlice(IEnumerable source, int start, int length) Assert.Equal(source.Skip(start).Take(length), ImmutableArray.Create(source.ToArray(), start, length)); } + [Theory] + [MemberData(nameof(CreateFromSliceData))] + public void SliceFromSliceData(IEnumerable source, int start, int length) + { + var immutableArray = source.ToImmutableArray(); + Assert.Equal(source.Skip(start).Take(length), immutableArray.Slice(start, length)); + } + public static IEnumerable CreateFromSliceData() { yield return new object[] { new int[] { }, 0, 0 }; @@ -417,6 +425,15 @@ public void CreateFromSliceOfImmutableArrayOptimizations(IEnumerable source Assert.True(array == slice); // Verify that the underlying arrays are reference-equal. } + [Theory] + [MemberData(nameof(Int32EnumerableData))] + public void SliceFromCompleteImmutableArrayOptimizations(IEnumerable source) + { + var array = source.ToImmutableArray(); + var slice = array.Slice(0, array.Length); + Assert.True(array == slice); // Verify that the underlying arrays are reference-equal. + } + [Theory] [MemberData(nameof(Int32EnumerableData))] public void CreateFromSliceOfImmutableArrayEmptyReturnsSingleton(IEnumerable source) @@ -445,6 +462,25 @@ public void CreateFromSliceOfArrayInvalid(IEnumerable source) } } + [Theory] + [MemberData(nameof(Int32EnumerableData))] + public void SliceFromInvalidRange(IEnumerable source) + { + var array = source.ToImmutableArray(); + + AssertExtensions.Throws("start", () => array.Slice(-1, 0)); + AssertExtensions.Throws("start", () => array.Slice(array.Length + 1, 0)); + + AssertExtensions.Throws("length", () => array.Slice(0, -1)); + AssertExtensions.Throws("length", () => array.Slice(0, array.Length + 1)); + AssertExtensions.Throws("length", () => array.Slice(Math.Max(0, array.Length - 1), 2)); + + if (array.Length > 0) + { + AssertExtensions.Throws("length", () => array.Slice(1, array.Length)); + } + } + [Theory] [MemberData(nameof(Int32EnumerableData))] public void CreateFromSliceOfArrayEmptyReturnsSingleton(IEnumerable source) @@ -454,6 +490,24 @@ public void CreateFromSliceOfArrayEmptyReturnsSingleton(IEnumerable source) Assert.True(s_empty == slice); } + [Theory] + [MemberData(nameof(Int32EnumerableData))] + public void SliceEmptyReturnsSingleton(IEnumerable source) + { + var array = source.ToImmutableArray(); + var slice = array.Slice(Math.Min(1, array.Length), 0); + Assert.True(s_empty == slice); + } + + [Theory] + [InlineData(0, 0)] + [InlineData(0, -1)] + [InlineData(-1, 0)] + public void SliceDefaultInvalid(int start, int length) + { + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.Slice(start, length)); + } + [Theory] [MemberData(nameof(Int32EnumerableData))] public void CreateFromArray(IEnumerable source) From f10ae911005eb720e56bd14820edd4bcc011d7ab Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Thu, 16 Dec 2021 18:15:17 +0800 Subject: [PATCH 14/29] Add test cases of Builder.CopyTo() and Builder.AddRange(). --- .../tests/ImmutableArrayBuilderTest.cs | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayBuilderTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayBuilderTest.cs index 5a70cb43e1e31..9bb72e704bf28 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayBuilderTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayBuilderTest.cs @@ -159,6 +159,22 @@ public void AddRangeDerivedArray() Assert.Equal(new[] { "a", "b" }, builder); } + [Fact] + public void AddRangeSpan() + { + var builder = new ImmutableArray.Builder(2); + builder.AddRange(new ReadOnlySpan(new[] { "a", "b", "c" })); + Assert.Equal(new[] { "a", "b", "c" }, builder); + } + + [Fact] + public void AddRangeDerivedSpan() + { + var builder = new ImmutableArray.Builder(); + builder.AddRange(new ReadOnlySpan(new[] { "a", "b" })); + Assert.Equal(new[] { "a", "b" }, builder); + } + [Fact] public void AddRangeDerivedImmutableArray() { @@ -478,7 +494,7 @@ public void ToImmutableArray() } [Fact] - public void CopyTo() + public void CopyToArray() { var builder = ImmutableArray.Create(1, 2, 3).ToBuilder(); var target = new int[4]; @@ -491,6 +507,30 @@ public void CopyTo() AssertExtensions.Throws("index", () => builder.CopyTo(target, 2)); } + [Fact] + public void CopyToSpan() + { + var builder = ImmutableArray.Create(1, 2, 3).ToBuilder(); + Span span; + int[] target = new int[4]; + + // Span is longer than immutableArray + span = new Span(target); + builder.CopyTo(span); + Assert.Equal(new[] { 1, 2, 3, 0 }, target); + span.Fill(0); + + // Span has same length as immutableArray + span = new Span(target, 0, 3); + builder.CopyTo(span); + Assert.Equal(new[] { 1, 2, 3, 0 }, target); + span.Fill(0); + + // Span is shorter than immutableArray + span = new Span(target, 0, 2); + AssertExtensions.Throws("destination", span, s => builder.CopyTo(s)); + } + [Fact] public void Clear() { From 4078a17ffc81e71562d1dd59e545d1e1fc24f99d Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Fri, 17 Dec 2021 11:16:12 +0800 Subject: [PATCH 15/29] Add xml doc for AsSpan(Range) method. --- .../src/System/Collections/Immutable/ImmutableArray_1.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs index c68e45d265e3e..ab8d42c287912 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs @@ -795,6 +795,11 @@ public ImmutableArray AddRange(params T[] items) public ReadOnlySpan AsSpan(int start, int length) => new ReadOnlySpan(array, start, length); #if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + /// + /// Creates a over the portion of current based on specified + /// + /// Range in current . + /// The representation of the public ReadOnlySpan AsSpan(Range range) { var self = this; From 932e73167b7d7829101cdc7ddf2d6f61520c00d1 Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Wed, 12 Jan 2022 17:23:38 +0800 Subject: [PATCH 16/29] Fix comment: refine test cases; adjust method list order; add Debug.Assert(). --- .../ref/System.Collections.Immutable.cs | 2 +- .../Collections/Immutable/ImmutableArray.cs | 6 +- .../Collections/Immutable/ImmutableArray_1.cs | 52 ++++++----- .../tests/ImmutableArrayBuilderTest.cs | 87 ++++++++++++++----- .../tests/ImmutableArrayTest.cs | 86 +++++++++--------- 5 files changed, 139 insertions(+), 94 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs b/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs index 16ccd8fe9c551..64ba5fdb56249 100644 --- a/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs +++ b/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs @@ -186,10 +186,10 @@ public void CopyTo(System.Span destination) { } public System.Collections.Immutable.ImmutableArray RemoveRange(int index, int length) { throw null; } public System.Collections.Immutable.ImmutableArray RemoveRange(System.ReadOnlySpan items, System.Collections.Generic.IEqualityComparer? equalityComparer = null) { throw null; } public System.Collections.Immutable.ImmutableArray RemoveRange(T[] items, System.Collections.Generic.IEqualityComparer? equalityComparer = null) { throw null; } - public System.Collections.Immutable.ImmutableArray Slice(int start, int length) { throw null; } public System.Collections.Immutable.ImmutableArray Replace(T oldValue, T newValue) { throw null; } public System.Collections.Immutable.ImmutableArray Replace(T oldValue, T newValue, System.Collections.Generic.IEqualityComparer? equalityComparer) { throw null; } public System.Collections.Immutable.ImmutableArray SetItem(int index, T item) { throw null; } + public System.Collections.Immutable.ImmutableArray Slice(int start, int length) { throw null; } public System.Collections.Immutable.ImmutableArray Sort() { throw null; } public System.Collections.Immutable.ImmutableArray Sort(System.Collections.Generic.IComparer? comparer) { throw null; } public System.Collections.Immutable.ImmutableArray Sort(System.Comparison comparison) { throw null; } diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray.cs index b9baf66aa1d2d..c3e2ed2f73525 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray.cs @@ -117,8 +117,7 @@ public static ImmutableArray Create(Span items) /// An immutable array. public static ImmutableArray ToImmutableArray(this ReadOnlySpan items) { - T[] array = items.ToArray(); - return new ImmutableArray(array); + return Create(items); } /// @@ -129,8 +128,7 @@ public static ImmutableArray ToImmutableArray(this ReadOnlySpan items) /// An immutable array. public static ImmutableArray ToImmutableArray(this Span items) { - T[] array = items.ToArray(); - return new ImmutableArray(array); + return Create((ReadOnlySpan)items); } /// diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs index ab8d42c287912..2180270760836 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs @@ -193,7 +193,7 @@ public int IndexOf(T item, int startIndex, int count, IEqualityComparer? equa public int LastIndexOf(T item) { var self = this; - if (self.Length == 0) + if (self.IsEmpty) { return -1; } @@ -210,7 +210,7 @@ public int LastIndexOf(T item) public int LastIndexOf(T item, int startIndex) { var self = this; - if (self.Length == 0 && startIndex == 0) + if (self.IsEmpty && startIndex == 0) { return -1; } @@ -292,7 +292,7 @@ public ImmutableArray Insert(int index, T item) self.ThrowNullRefIfNotInitialized(); Requires.Range(index >= 0 && index <= self.Length, nameof(index)); - if (self.Length == 0) + if (self.IsEmpty) { return ImmutableArray.Create(item); } @@ -325,7 +325,7 @@ public ImmutableArray InsertRange(int index, IEnumerable items) Requires.Range(index >= 0 && index <= self.Length, nameof(index)); Requires.NotNull(items, nameof(items)); - if (self.Length == 0) + if (self.IsEmpty) { return ImmutableArray.CreateRange(items); } @@ -382,7 +382,7 @@ public ImmutableArray InsertRange(int index, ImmutableArray items) { return items; } - else if (items.IsEmpty) + if (items.IsEmpty) { return self; } @@ -398,7 +398,7 @@ public ImmutableArray InsertRange(int index, ImmutableArray items) public ImmutableArray Add(T item) { var self = this; - if (self.Length == 0) + if (self.IsEmpty) { return ImmutableArray.Create(item); } @@ -840,7 +840,7 @@ public ImmutableArray InsertRange(int index, T[] items) { return self; } - if (self.Length == 0) + if (self.IsEmpty) { return new ImmutableArray(items); } @@ -848,22 +848,6 @@ public ImmutableArray InsertRange(int index, T[] items) return self.InsertSpanRangeInternal(index, items); } - private ImmutableArray InsertSpanRangeInternal(int index, ReadOnlySpan items) - { - var tmp = new T[Length + items.Length]; - if (index != 0) - { - Array.Copy(array!, tmp, index); - } - items.CopyTo(new Span(tmp, index, items.Length)); - if (index != Length) - { - Array.Copy(array!, index, tmp, index + items.Length, Length - index); - } - - return new ImmutableArray(tmp); - } - /// /// Inserts the specified values at the specified index. /// @@ -880,7 +864,7 @@ public ImmutableArray InsertRange(int index, ReadOnlySpan items) { return self; } - if (self.Length == 0) + if (self.IsEmpty) { return items.ToImmutableArray(); } @@ -1410,5 +1394,25 @@ private ImmutableArray RemoveAtRange(ICollection indicesToRemove) return new ImmutableArray(newArray); } + + private ImmutableArray InsertSpanRangeInternal(int index, ReadOnlySpan items) + { + Debug.Assert(array != null); + Debug.Assert(!IsEmpty); + Debug.Assert(!items.IsEmpty); + + var tmp = new T[Length + items.Length]; + if (index != 0) + { + Array.Copy(array!, tmp, index); + } + items.CopyTo(new Span(tmp, index, items.Length)); + if (index != Length) + { + Array.Copy(array!, index, tmp, index + items.Length, Length - index); + } + + return new ImmutableArray(tmp); + } } } diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayBuilderTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayBuilderTest.cs index 9bb72e704bf28..5b34fe28d84ff 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayBuilderTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayBuilderTest.cs @@ -12,6 +12,13 @@ namespace System.Collections.Immutable.Tests { public class ImmutableArrayBuilderTest : SimpleElementImmutablesTestBase { + public static IEnumerable BuilderAddRangeData() + { + yield return new object[] { new[] { "a", "b" }, Array.Empty(), new[] { "a", "b" } }; + yield return new object[] { Array.Empty(), new[] { "a", "b" }, new[] { "a", "b" } }; + yield return new object[] { new[] { "a", "b" }, new[] { "c", "d" }, new[] { "a", "b", "c", "d" } }; + } + [Fact] public void CreateBuilderDefaultCapacity() { @@ -151,47 +158,83 @@ public void AddRangeImmutableArray() AssertExtensions.Throws("items", () => builder2.AddRange((ImmutableArray.Builder)null)); } - [Fact] - public void AddRangeDerivedArray() + [Theory] + [MemberData(nameof(BuilderAddRangeData))] + public void AddRangeDerivedArray(string[] builderElements, string[] rangeElements, string[] expectedResult) { + // Initialize builder var builder = new ImmutableArray.Builder(); - builder.AddRange(new[] { "a", "b" }); - Assert.Equal(new[] { "a", "b" }, builder); + builder.AddRange(builderElements); + + // AddRange + builder.AddRange(rangeElements); + + // Assert + Assert.Equal(expectedResult, builder); } - [Fact] - public void AddRangeSpan() + [Theory] + [MemberData(nameof(BuilderAddRangeData))] + public void AddRangeSpan(string[] builderElements, string[] rangeElements, string[] expectedResult) { - var builder = new ImmutableArray.Builder(2); - builder.AddRange(new ReadOnlySpan(new[] { "a", "b", "c" })); - Assert.Equal(new[] { "a", "b", "c" }, builder); + // Initialize builder + var builder = new ImmutableArray.Builder(); + builder.AddRange(builderElements); + + // AddRange + builder.AddRange(new ReadOnlySpan(rangeElements)); + + // Assert + Assert.Equal(expectedResult, builder); } - [Fact] - public void AddRangeDerivedSpan() + [Theory] + [MemberData(nameof(BuilderAddRangeData))] + public void AddRangeDerivedSpan(string[] builderElements, string[] rangeElements, string[] expectedResult) { + // Initialize builder var builder = new ImmutableArray.Builder(); - builder.AddRange(new ReadOnlySpan(new[] { "a", "b" })); - Assert.Equal(new[] { "a", "b" }, builder); + builder.AddRange(builderElements); + + // AddRange + builder.AddRange(new ReadOnlySpan(rangeElements)); + + // Assert + Assert.Equal(expectedResult, builder); } - [Fact] - public void AddRangeDerivedImmutableArray() + [Theory] + [MemberData(nameof(BuilderAddRangeData))] + public void AddRangeDerivedImmutableArray(string[] builderElements, string[] rangeElements, string[] expectedResult) { + // Initialize builder var builder = new ImmutableArray.Builder(); - builder.AddRange(new[] { "a", "b" }.ToImmutableArray()); - Assert.Equal(new[] { "a", "b" }, builder); + builder.AddRange(builderElements); + + // AddRange + builder.AddRange(rangeElements.ToImmutableArray()); + + // Assert + Assert.Equal(expectedResult, builder); } - [Fact] - public void AddRangeDerivedBuilder() + [Theory] + [MemberData(nameof(BuilderAddRangeData))] + public void AddRangeDerivedBuilder(string[] builderElements, string[] rangeElements, string[] expectedResult) { + // Initialize builder + var builderBase = new ImmutableArray.Builder(); + builderBase.AddRange(builderElements); + + // Prepare another builder to add var builder = new ImmutableArray.Builder(); - builder.AddRange(new[] { "a", "b" }); + builder.AddRange(rangeElements); - var builderBase = new ImmutableArray.Builder(); + // AddRange builderBase.AddRange(builder); - Assert.Equal(new[] { "a", "b" }, builderBase); + + // Assert + Assert.Equal(expectedResult, builderBase); } [Fact] diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs index 6a331e988c1fd..7f769ac03e9a0 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs @@ -47,6 +47,17 @@ public static IEnumerable StringImmutableArrayData() yield return new object[] { new[] { (string)null } }; } + public static IEnumerable RangeIndexLengthData() + { + yield return new object[] { s_empty, 0, 0 }; + yield return new object[] { s_oneElement, 1, 0 }; + yield return new object[] { s_oneElement, 0, 1 }; + yield return new object[] { s_oneElement, 0, 0 }; + yield return new object[] { new[] { 1, 2, 3, 4 }, 0, 2 }; + yield return new object[] { new[] { 1, 2, 3, 4 }, 1, 2 }; + yield return new object[] { new[] { 1, 2, 3, 4 }, 2, 2 }; + } + [Theory] [MemberData(nameof(Int32EnumerableData))] public void Clear(IEnumerable source) @@ -132,6 +143,38 @@ public void AsSpanEmptyRangeNotInitialized() } #endif + [Theory] + [MemberData(nameof(RangeIndexLengthData))] + public void AsSpanStartLength(IEnumerable source, int start, int length) + { + var array = source.ToImmutableArray(); + var expected = source.Skip(start).Take(length); + Assert.Equal(expected, array.AsSpan(start, length).ToArray()); + +#if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + Assert.Equal(expected, array.AsSpan(new Range(start, start + length)).ToArray()); +#endif + } + + [Theory] + [MemberData(nameof(Int32EnumerableData))] + public void AsSpanStartLengthInvalid(IEnumerable source) + { + var array = source.ToImmutableArray(); + + AssertExtensions.Throws(() => array.AsSpan(-1, 1)); + AssertExtensions.Throws(() => array.AsSpan(array.Length + 1, 1)); + AssertExtensions.Throws(() => array.AsSpan(0, -1)); + AssertExtensions.Throws(() => array.AsSpan(0, array.Length + 1)); + +#if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + AssertExtensions.Throws(() => array.AsSpan(new Range(-1, 0))); + AssertExtensions.Throws(() => array.AsSpan(new Range(array.Length + 1, array.Length + 2))); + AssertExtensions.Throws(() => array.AsSpan(new Range(0, -1))); + AssertExtensions.Throws(() => array.AsSpan(new Range(0, array.Length + 1))); +#endif + } + [Theory] [MemberData(nameof(Int32EnumerableData))] public void AsMemoryRoundTripTests(IEnumerable source) @@ -1434,49 +1477,6 @@ public void RemoveRangeIndexLength(IEnumerable source, int index, int lengt Assert.Equal(expected, array.RemoveRange(index, length)); } - [Theory] - [MemberData(nameof(RangeIndexLengthData))] - public void AsSpanStartLength(IEnumerable source, int start, int length) - { - var array = source.ToImmutableArray(); - var expected = source.Skip(start).Take(length); - Assert.Equal(expected, array.AsSpan(start, length).ToArray()); - -#if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER - Assert.Equal(expected, array.AsSpan(new Range(start, start + length)).ToArray()); -#endif - } - - [Theory] - [MemberData(nameof(Int32EnumerableData))] - public void AsSpanStartLengthInvalid(IEnumerable source) - { - var array = source.ToImmutableArray(); - - AssertExtensions.Throws(() => array.AsSpan(-1, 1)); - AssertExtensions.Throws(() => array.AsSpan(array.Length + 1, 1)); - AssertExtensions.Throws(() => array.AsSpan(0, -1)); - AssertExtensions.Throws(() => array.AsSpan(0, array.Length + 1)); - -#if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER - AssertExtensions.Throws(() => array.AsSpan(new Range(-1, 0))); - AssertExtensions.Throws(() => array.AsSpan(new Range(array.Length + 1, array.Length + 2))); - AssertExtensions.Throws(() => array.AsSpan(new Range(0, -1))); - AssertExtensions.Throws(() => array.AsSpan(new Range(0, array.Length + 1))); -#endif - } - - public static IEnumerable RangeIndexLengthData() - { - yield return new object[] { s_empty, 0, 0 }; - yield return new object[] { s_oneElement, 1, 0 }; - yield return new object[] { s_oneElement, 0, 1 }; - yield return new object[] { s_oneElement, 0, 0 }; - yield return new object[] { new[] { 1, 2, 3, 4 }, 0, 2 }; - yield return new object[] { new[] { 1, 2, 3, 4 }, 1, 2 }; - yield return new object[] { new[] { 1, 2, 3, 4 }, 2, 2 }; - } - [Theory] [MemberData(nameof(Int32EnumerableData))] public void RemoveRangeIndexLengthInvalid(IEnumerable source) From c4384dfe04629d3a81939e3b840e93b12fbd7ee2 Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Fri, 14 Jan 2022 18:32:44 +0800 Subject: [PATCH 17/29] Fix comment: move tfm specific code into separate tfm files. --- .../ref/System.Collections.Immutable.cs | 3 -- .../ref/System.Collections.Immutable.csproj | 1 + ...tem.Collections.Immutable.netstandard21.cs | 13 +++++ .../src/System.Collections.Immutable.csproj | 1 + .../Collections/Immutable/ImmutableArray_1.cs | 16 ------ .../ImmutableArray_1.netstandard21.cs | 24 +++++++++ .../tests/ImmutableArrayTest.cs | 27 +--------- .../tests/ImmutableArrayTest.netstandard21.cs | 50 +++++++++++++++++++ .../System.Collections.Immutable.Tests.csproj | 1 + 9 files changed, 91 insertions(+), 45 deletions(-) create mode 100644 src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.netstandard21.cs create mode 100644 src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.netstandard21.cs create mode 100644 src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.netstandard21.cs diff --git a/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs b/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs index 64ba5fdb56249..c04f557cf5437 100644 --- a/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs +++ b/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs @@ -127,9 +127,6 @@ public static partial class ImmutableArray public System.ReadOnlyMemory AsMemory() { throw null; } public System.ReadOnlySpan AsSpan() { throw null; } public System.ReadOnlySpan AsSpan(int start, int length) { throw null; } -#if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER - public System.ReadOnlySpan AsSpan(System.Range range) { throw null; } -#endif public System.Collections.Immutable.ImmutableArray< #nullable disable TOther diff --git a/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.csproj b/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.csproj index 01c989df4fef6..ad3ce65cd20ae 100644 --- a/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.csproj +++ b/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.csproj @@ -5,6 +5,7 @@ + diff --git a/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.netstandard21.cs b/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.netstandard21.cs new file mode 100644 index 0000000000000..d6c786d33e236 --- /dev/null +++ b/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.netstandard21.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// ------------------------------------------------------------------------------ +// Changes to this file must follow the https://aka.ms/api-review process. +// ------------------------------------------------------------------------------ + +namespace System.Collections.Immutable +{ + public readonly partial struct ImmutableArray : System.Collections.Generic.ICollection, System.Collections.Generic.IEnumerable, System.Collections.Generic.IList, System.Collections.Generic.IReadOnlyCollection, System.Collections.Generic.IReadOnlyList, System.Collections.ICollection, System.Collections.IEnumerable, System.Collections.IList, System.Collections.Immutable.IImmutableList, System.Collections.IStructuralComparable, System.Collections.IStructuralEquatable, System.IEquatable> + { + public System.ReadOnlySpan AsSpan(System.Range range) { throw null; } + } +} diff --git a/src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj b/src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj index 9f95b79a7d3ab..13b758e37a1b3 100644 --- a/src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj +++ b/src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj @@ -49,6 +49,7 @@ System.Collections.Immutable.ImmutableStack<T> + diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs index 2180270760836..2ca3d661fa694 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs @@ -794,22 +794,6 @@ public ImmutableArray AddRange(params T[] items) /// The representation of the public ReadOnlySpan AsSpan(int start, int length) => new ReadOnlySpan(array, start, length); -#if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER - /// - /// Creates a over the portion of current based on specified - /// - /// Range in current . - /// The representation of the - public ReadOnlySpan AsSpan(Range range) - { - var self = this; - self.ThrowNullRefIfNotInitialized(); - - (int start, int length) = range.GetOffsetAndLength(self.Length); - return new ReadOnlySpan(self.array, start, length); - } -#endif - /// /// Copies the elements of current to an . /// diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.netstandard21.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.netstandard21.cs new file mode 100644 index 0000000000000..cf5c0013b7a31 --- /dev/null +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.netstandard21.cs @@ -0,0 +1,24 @@ +// 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; + +namespace System.Collections.Immutable +{ + public readonly partial struct ImmutableArray : IReadOnlyList, IList, IEquatable>, IList, IImmutableArray, IStructuralComparable, IStructuralEquatable, IImmutableList + { + /// + /// Creates a over the portion of current based on specified + /// + /// Range in current . + /// The representation of the + public ReadOnlySpan AsSpan(Range range) + { + var self = this; + self.ThrowNullRefIfNotInitialized(); + + (int start, int length) = range.GetOffsetAndLength(self.Length); + return new ReadOnlySpan(self.array, start, length); + } + } +} diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs index 7f769ac03e9a0..76e42ad0a8e8a 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs @@ -14,7 +14,7 @@ namespace System.Collections.Immutable.Tests { - public class ImmutableArrayTest : SimpleElementImmutablesTestBase + public partial class ImmutableArrayTest : SimpleElementImmutablesTestBase { private static readonly ImmutableArray s_emptyDefault = default; // init explicitly to avoid CS0649 private static readonly ImmutableArray s_empty = ImmutableArray.Create(); @@ -87,12 +87,6 @@ public void AsSpanRoundTripEmptyArrayTests() ReadOnlySpan startRangedSpan = immutableArray.AsSpan(0, 0); Assert.Equal(immutableArray, startRangedSpan.ToArray()); Assert.Equal(immutableArray.Length, startRangedSpan.Length); - -#if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER - ReadOnlySpan rangedSpan = immutableArray.AsSpan(new Range(0, 0)); - Assert.Equal(immutableArray, rangedSpan.ToArray()); - Assert.Equal(immutableArray.Length, rangedSpan.Length); -#endif } [Fact] @@ -135,14 +129,6 @@ public void AsSpanRoundTripDefaultArrayStringTests() Assert.True(startRangeSpan.IsEmpty); } -#if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER - [Fact] - public void AsSpanEmptyRangeNotInitialized() - { - TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.AsSpan(new Range(0, 0))); - } -#endif - [Theory] [MemberData(nameof(RangeIndexLengthData))] public void AsSpanStartLength(IEnumerable source, int start, int length) @@ -150,10 +136,6 @@ public void AsSpanStartLength(IEnumerable source, int start, int length) var array = source.ToImmutableArray(); var expected = source.Skip(start).Take(length); Assert.Equal(expected, array.AsSpan(start, length).ToArray()); - -#if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER - Assert.Equal(expected, array.AsSpan(new Range(start, start + length)).ToArray()); -#endif } [Theory] @@ -166,13 +148,6 @@ public void AsSpanStartLengthInvalid(IEnumerable source) AssertExtensions.Throws(() => array.AsSpan(array.Length + 1, 1)); AssertExtensions.Throws(() => array.AsSpan(0, -1)); AssertExtensions.Throws(() => array.AsSpan(0, array.Length + 1)); - -#if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER - AssertExtensions.Throws(() => array.AsSpan(new Range(-1, 0))); - AssertExtensions.Throws(() => array.AsSpan(new Range(array.Length + 1, array.Length + 2))); - AssertExtensions.Throws(() => array.AsSpan(new Range(0, -1))); - AssertExtensions.Throws(() => array.AsSpan(new Range(0, array.Length + 1))); -#endif } [Theory] diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.netstandard21.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.netstandard21.cs new file mode 100644 index 0000000000000..5821f93fc8c5b --- /dev/null +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.netstandard21.cs @@ -0,0 +1,50 @@ +// 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.Linq; +using Xunit; + +namespace System.Collections.Immutable.Tests +{ + public partial class ImmutableArrayTest : SimpleElementImmutablesTestBase + { + [Fact] + public void AsSpanRoundTripEmptyArrayTests_RangeInput() + { + ImmutableArray immutableArray = ImmutableArray.Create(Array.Empty()); + + ReadOnlySpan rangedSpan = immutableArray.AsSpan(new Range(0, 0)); + Assert.Equal(immutableArray, rangedSpan.ToArray()); + Assert.Equal(immutableArray.Length, rangedSpan.Length); + } + + [Fact] + public void AsSpanEmptyRangeNotInitialized() + { + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.AsSpan(new Range(0, 0))); + } + + [Theory] + [MemberData(nameof(RangeIndexLengthData))] + public void AsSpanStartLength_RangeInput(IEnumerable source, int start, int length) + { + var array = source.ToImmutableArray(); + var expected = source.Skip(start).Take(length); + + Assert.Equal(expected, array.AsSpan(new Range(start, start + length)).ToArray()); + } + + [Theory] + [MemberData(nameof(Int32EnumerableData))] + public void AsSpanStartLengthInvalid_RangeInput(IEnumerable source) + { + var array = source.ToImmutableArray(); + + AssertExtensions.Throws(() => array.AsSpan(new Range(-1, 0))); + AssertExtensions.Throws(() => array.AsSpan(new Range(array.Length + 1, array.Length + 2))); + AssertExtensions.Throws(() => array.AsSpan(new Range(0, -1))); + AssertExtensions.Throws(() => array.AsSpan(new Range(0, array.Length + 1))); + } + } +} diff --git a/src/libraries/System.Collections.Immutable/tests/System.Collections.Immutable.Tests.csproj b/src/libraries/System.Collections.Immutable/tests/System.Collections.Immutable.Tests.csproj index 5dc59a3838215..082bc665f341b 100644 --- a/src/libraries/System.Collections.Immutable/tests/System.Collections.Immutable.Tests.csproj +++ b/src/libraries/System.Collections.Immutable/tests/System.Collections.Immutable.Tests.csproj @@ -11,6 +11,7 @@ + From c470255280e130e5e0aec87bb88ca14c57b40bde Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Tue, 18 Jan 2022 18:15:10 +0800 Subject: [PATCH 18/29] Fix ValidateDefaultThisBehavior() logic issue. --- .../tests/TestExtensionsMethods.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Collections.Immutable/tests/TestExtensionsMethods.cs b/src/libraries/System.Collections.Immutable/tests/TestExtensionsMethods.cs index cd3138e1a3442..2e2c65d491e3d 100644 --- a/src/libraries/System.Collections.Immutable/tests/TestExtensionsMethods.cs +++ b/src/libraries/System.Collections.Immutable/tests/TestExtensionsMethods.cs @@ -21,7 +21,6 @@ internal static void ValidateDefaultThisBehavior(ReadOnlySpan span, try { action(span); - throw new ThrowsException(typeof(NullReferenceException)); } catch (NullReferenceException nullRefEx) when (nullRefEx.GetType() == typeof(NullReferenceException)) { @@ -31,6 +30,8 @@ internal static void ValidateDefaultThisBehavior(ReadOnlySpan span, { throw new ThrowsException(typeof(NullReferenceException), ex); } + + throw new ThrowsException(typeof(NullReferenceException)); } } } From b29f1058b6b527c1b28249d3635b341135decc19 Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Thu, 20 Jan 2022 11:10:53 +0800 Subject: [PATCH 19/29] Fix comment: use compile condition: $(TargetFrameworkIdentifier)' == '.NETCoreApp'. --- .../ref/System.Collections.Immutable.csproj | 2 +- ...standard21.cs => System.Collections.Immutable.netcoreapp.cs} | 0 .../src/System.Collections.Immutable.csproj | 2 +- ...eArray_1.netstandard21.cs => ImmutableArray_1.netcoreapp.cs} | 0 ...ayTest.netstandard21.cs => ImmutableArrayTest.netcoreapp.cs} | 0 .../tests/System.Collections.Immutable.Tests.csproj | 2 +- 6 files changed, 3 insertions(+), 3 deletions(-) rename src/libraries/System.Collections.Immutable/ref/{System.Collections.Immutable.netstandard21.cs => System.Collections.Immutable.netcoreapp.cs} (100%) rename src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/{ImmutableArray_1.netstandard21.cs => ImmutableArray_1.netcoreapp.cs} (100%) rename src/libraries/System.Collections.Immutable/tests/{ImmutableArrayTest.netstandard21.cs => ImmutableArrayTest.netcoreapp.cs} (100%) diff --git a/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.csproj b/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.csproj index ad3ce65cd20ae..10f55a7ef5287 100644 --- a/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.csproj +++ b/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.csproj @@ -5,7 +5,7 @@ - + diff --git a/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.netstandard21.cs b/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.netcoreapp.cs similarity index 100% rename from src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.netstandard21.cs rename to src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.netcoreapp.cs diff --git a/src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj b/src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj index 13b758e37a1b3..c66bec757e410 100644 --- a/src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj +++ b/src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj @@ -49,7 +49,7 @@ System.Collections.Immutable.ImmutableStack<T> - + diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.netstandard21.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.netcoreapp.cs similarity index 100% rename from src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.netstandard21.cs rename to src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.netcoreapp.cs diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.netstandard21.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.netcoreapp.cs similarity index 100% rename from src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.netstandard21.cs rename to src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.netcoreapp.cs diff --git a/src/libraries/System.Collections.Immutable/tests/System.Collections.Immutable.Tests.csproj b/src/libraries/System.Collections.Immutable/tests/System.Collections.Immutable.Tests.csproj index 082bc665f341b..ecd0a47fae59c 100644 --- a/src/libraries/System.Collections.Immutable/tests/System.Collections.Immutable.Tests.csproj +++ b/src/libraries/System.Collections.Immutable/tests/System.Collections.Immutable.Tests.csproj @@ -11,7 +11,7 @@ - + From d98e0d9bafeeae054e7a83cb15194c751164b767 Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Thu, 20 Jan 2022 14:30:44 +0800 Subject: [PATCH 20/29] Fix comment: specify actual data length when construct span. --- .../src/System/Collections/Immutable/ImmutableArray.cs | 2 +- .../System/Collections/Immutable/ImmutableArray_1.Builder.cs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray.cs index c3e2ed2f73525..664fc2b48e550 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray.cs @@ -89,7 +89,7 @@ public static ImmutableArray Create(T item1, T item2, T item3, T item4) /// An immutable array. public static ImmutableArray Create(ReadOnlySpan items) { - if (items.Length == 0) + if (items.IsEmpty) { return ImmutableArray.Empty; } diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs index 28cfa5924ccad..8e535aebf20f4 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs @@ -366,9 +366,10 @@ public void AddRange(ReadOnlySpan items) where TDerived : T int offset = this.Count; this.Count += items.Length; + var elements = new Span(_elements, offset, items.Length); for (int i = 0; i < items.Length; i++) { - _elements[offset + i] = items[i]; + elements[i] = items[i]; } } @@ -739,7 +740,7 @@ public void Sort(int index, int count, IComparer? comparer) public void CopyTo(Span destination) { Requires.Range(this.Count <= destination.Length, nameof(destination)); - new ReadOnlySpan(_elements).CopyTo(destination); + new ReadOnlySpan(_elements, 0, this.Count).CopyTo(destination); } /// From 50e278d7158e64b75e045ae098fac5f730c87091 Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Fri, 21 Jan 2022 18:45:19 +0800 Subject: [PATCH 21/29] Change RemoveRange(ReadOnlySpan) to be O(M+N) implementation. --- .../Collections/Immutable/ImmutableArray_1.cs | 61 ++++++++++++++++--- .../tests/ImmutableArrayTest.cs | 4 +- 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs index 2ca3d661fa694..0e2b793de311a 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs @@ -881,17 +881,64 @@ public ImmutableArray RemoveRange(ReadOnlySpan items, IEqualityComparer return self.Remove(items[0], equalityComparer); } - var indicesToRemove = new SortedSet(); - foreach (var item in items) +#nullable disable + var itemsDic = new Dictionary(equalityComparer); +#nullable restore + int nullValueCount = 0; + foreach (ref readonly T item in items) { - int index = -1; - do + if (item == null) { - index = self.IndexOf(item, index + 1, equalityComparer); - } while (index >= 0 && !indicesToRemove.Add(index) && index < self.Length - 1); + nullValueCount++; + } + else if (itemsDic.TryGetValue(item, out int count)) + { + itemsDic[item] = count + 1; + } + else + { + itemsDic[item] = 1; + } } - return self.RemoveAtRange(indicesToRemove); + List? indicesToRemove = null; + T[] selfArray = self.array!; + for (int i = 0; i < selfArray.Length; i++) + { + bool found = false; + if (selfArray[i] == null) + { + if (nullValueCount > 0) + { + found = true; + nullValueCount--; + } + } + else if (itemsDic.TryGetValue(selfArray[i], out int count)) + { + found = true; + if (count == 1) + { + itemsDic.Remove(selfArray[i]); + } + else + { + Debug.Assert(count > 1); + itemsDic[selfArray[i]] = count - 1; + } + } + + if (found) + { + if (indicesToRemove == null) + { + indicesToRemove = new List(); + } + indicesToRemove.Add(i); + } + } + + return indicesToRemove == null ? self : self.RemoveAtRange(indicesToRemove); } /// diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs index 76e42ad0a8e8a..bccc564288a6a 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs @@ -2620,8 +2620,8 @@ private static IEnumerable> SharedEqualityComparers() // Null comparers should be accepted and translated to the default comparer. yield return null; yield return EqualityComparer.Default; - yield return new DelegateEqualityComparer(equals: (x, y) => true, objectGetHashCode: obj => 0); - yield return new DelegateEqualityComparer(equals: (x, y) => false, objectGetHashCode: obj => 0); + yield return new DelegateEqualityComparer(equals: (x, y) => true, objectGetHashCode: obj => 0, getHashCode: obj => 0); + yield return new DelegateEqualityComparer(equals: (x, y) => false, objectGetHashCode: obj => 0, getHashCode: obj => 0); } /// From ba0fa13d85136b5c190e3659f4e2a13156614dc9 Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Fri, 21 Jan 2022 19:01:47 +0800 Subject: [PATCH 22/29] Fix comment: refine xml doc. --- .../Collections/Immutable/ImmutableArray.cs | 22 +++++++++---------- .../Immutable/ImmutableArray_1.Builder.cs | 10 ++++----- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray.cs index 664fc2b48e550..5830fa943a47a 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray.cs @@ -32,7 +32,7 @@ public static ImmutableArray Create() /// /// The type of element stored in the array. /// The element to store in the array. - /// A 1-element array. + /// A 1-element immutable array containing the specified item. public static ImmutableArray Create(T item) { T[] array = new[] { item }; @@ -45,7 +45,7 @@ public static ImmutableArray Create(T item) /// The type of element stored in the array. /// The first element to store in the array. /// The second element to store in the array. - /// A 2-element array. + /// A 2-element immutable array containing the specified items. public static ImmutableArray Create(T item1, T item2) { T[] array = new[] { item1, item2 }; @@ -59,7 +59,7 @@ public static ImmutableArray Create(T item1, T item2) /// The first element to store in the array. /// The second element to store in the array. /// The third element to store in the array. - /// A 3-element array. + /// A 3-element immutable array containing the specified items. public static ImmutableArray Create(T item1, T item2, T item3) { T[] array = new[] { item1, item2, item3 }; @@ -74,7 +74,7 @@ public static ImmutableArray Create(T item1, T item2, T item3) /// The second element to store in the array. /// The third element to store in the array. /// The fourth element to store in the array. - /// A 4-element array. + /// A 4-element immutable array containing the specified items. public static ImmutableArray Create(T item1, T item2, T item3, T item4) { T[] array = new[] { item1, item2, item3, item4 }; @@ -86,7 +86,7 @@ public static ImmutableArray Create(T item1, T item2, T item3, T item4) /// /// The type of element stored in the array. /// The elements to store in the array. - /// An immutable array. + /// An immutable array containing the specified items. public static ImmutableArray Create(ReadOnlySpan items) { if (items.IsEmpty) @@ -103,7 +103,7 @@ public static ImmutableArray Create(ReadOnlySpan items) /// /// The type of element stored in the array. /// The elements to store in the array. - /// An immutable array. + /// An immutable array containing the specified items. public static ImmutableArray Create(Span items) { return Create((ReadOnlySpan)items); @@ -114,7 +114,7 @@ public static ImmutableArray Create(Span items) /// /// The type of element in the list. /// The elements to store in the array. - /// An immutable array. + /// An immutable array containing the specified items. public static ImmutableArray ToImmutableArray(this ReadOnlySpan items) { return Create(items); @@ -125,7 +125,7 @@ public static ImmutableArray ToImmutableArray(this ReadOnlySpan items) /// /// The type of element in the list. /// The elements to store in the array. - /// An immutable array. + /// An immutable array containing the specified items. public static ImmutableArray ToImmutableArray(this Span items) { return Create((ReadOnlySpan)items); @@ -183,7 +183,7 @@ public static ImmutableArray CreateRange(IEnumerable items) /// /// The type of element stored in the array. /// The elements to store in the array. - /// An immutable array. + /// An immutable array containing the specified items. public static ImmutableArray Create(params T[]? items) { if (items == null || items.Length == 0) @@ -418,7 +418,7 @@ public static ImmutableArray.Builder CreateBuilder(int initialCapacity) /// /// The type of element in the sequence. /// The sequence to enumerate. - /// An immutable array. + /// An immutable array containing the specified items. public static ImmutableArray ToImmutableArray(this IEnumerable items) { if (items is ImmutableArray) @@ -433,7 +433,7 @@ public static ImmutableArray ToImmutableArray(this IEnumerable /// Returns an immutable copy of the current contents of the builder's collection. /// /// The builder to create the immutable array from. - /// An immutable array. + /// An immutable array containing the specified items from . public static ImmutableArray ToImmutableArray(this ImmutableArray.Builder builder) { Requires.NotNull(builder, nameof(builder)); diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs index 8e535aebf20f4..924a919205a38 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs @@ -348,7 +348,7 @@ public void AddRange(ImmutableArray items, int length) /// /// Adds the specified items to the end of the array. /// - /// The items. + /// The items to add at the end of the array. public void AddRange(ReadOnlySpan items) { int offset = this.Count; @@ -360,7 +360,7 @@ public void AddRange(ReadOnlySpan items) /// /// Adds the specified items to the end of the array. /// - /// The items. + /// The items to add at the end of the array. public void AddRange(ReadOnlySpan items) where TDerived : T { int offset = this.Count; @@ -376,7 +376,7 @@ public void AddRange(ReadOnlySpan items) where TDerived : T /// /// Adds the specified items to the end of the array. /// - /// The items. + /// The items to add at the end of the array. public void AddRange(ImmutableArray items) where TDerived : T { if (items.array != null) @@ -388,7 +388,7 @@ public void AddRange(ImmutableArray items) where TDerived : /// /// Adds the specified items to the end of the array. /// - /// The items. + /// The items to add at the end of the array. public void AddRange(Builder items) { Requires.NotNull(items, nameof(items)); @@ -398,7 +398,7 @@ public void AddRange(Builder items) /// /// Adds the specified items to the end of the array. /// - /// The items. + /// The items to add at the end of the array. public void AddRange(ImmutableArray.Builder items) where TDerived : T { Requires.NotNull(items, nameof(items)); From fb1dd63dd0671170da20f48d1899c3552c573955 Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Mon, 24 Jan 2022 16:08:57 +0800 Subject: [PATCH 23/29] Fix comment: Use new CollectionsMarshal methods to get and update value in Dictionary; split netcoreapp RemoveRange and non-netcoreapp RemoveRange(). --- .../src/System.Collections.Immutable.csproj | 1 + .../Collections/Immutable/ImmutableArray_1.cs | 89 +----------------- .../Immutable/ImmutableArray_1.netcoreapp.cs | 90 ++++++++++++++++++ .../ImmutableArray_1.nonnetcoreapp.cs | 93 +++++++++++++++++++ 4 files changed, 186 insertions(+), 87 deletions(-) create mode 100644 src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.nonnetcoreapp.cs diff --git a/src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj b/src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj index c66bec757e410..7348336c3baab 100644 --- a/src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj +++ b/src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj @@ -50,6 +50,7 @@ System.Collections.Immutable.ImmutableStack<T> + diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs index 0e2b793de311a..3c3cd68e5c146 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs @@ -856,91 +856,6 @@ public ImmutableArray InsertRange(int index, ReadOnlySpan items) return self.InsertSpanRangeInternal(index, items); } - /// - /// Removes the specified values from this list. - /// - /// The items to remove if matches are found in this list. - /// - /// The equality comparer to use in the search. - /// - /// - /// A new list with the elements removed. - /// - public ImmutableArray RemoveRange(ReadOnlySpan items, IEqualityComparer? equalityComparer = null) - { - var self = this; - self.ThrowNullRefIfNotInitialized(); - - if (items.IsEmpty || self.IsEmpty) - { - return self; - } - - if (items.Length == 1) - { - return self.Remove(items[0], equalityComparer); - } - -#nullable disable - var itemsDic = new Dictionary(equalityComparer); -#nullable restore - int nullValueCount = 0; - foreach (ref readonly T item in items) - { - if (item == null) - { - nullValueCount++; - } - else if (itemsDic.TryGetValue(item, out int count)) - { - itemsDic[item] = count + 1; - } - else - { - itemsDic[item] = 1; - } - } - - List? indicesToRemove = null; - T[] selfArray = self.array!; - for (int i = 0; i < selfArray.Length; i++) - { - bool found = false; - if (selfArray[i] == null) - { - if (nullValueCount > 0) - { - found = true; - nullValueCount--; - } - } - else if (itemsDic.TryGetValue(selfArray[i], out int count)) - { - found = true; - if (count == 1) - { - itemsDic.Remove(selfArray[i]); - } - else - { - Debug.Assert(count > 1); - itemsDic[selfArray[i]] = count - 1; - } - } - - if (found) - { - if (indicesToRemove == null) - { - indicesToRemove = new List(); - } - indicesToRemove.Add(i); - } - } - - return indicesToRemove == null ? self : self.RemoveAtRange(indicesToRemove); - } - /// /// Removes the specified values from this list. /// @@ -974,7 +889,7 @@ public ImmutableArray Slice(int start, int length) return ImmutableArray.Create(self, start, length); } - #region Explicit interface methods +#region Explicit interface methods void IList.Insert(int index, T item) { @@ -1387,7 +1302,7 @@ int IStructuralComparable.CompareTo(object? other, IComparer comparer) throw new ArgumentException(SR.ArrayLengthsNotEqual, nameof(other)); } - #endregion +#endregion /// diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.netcoreapp.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.netcoreapp.cs index cf5c0013b7a31..ca50c8d4eb8ac 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.netcoreapp.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.netcoreapp.cs @@ -2,6 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace System.Collections.Immutable { @@ -20,5 +23,92 @@ public ReadOnlySpan AsSpan(Range range) (int start, int length) = range.GetOffsetAndLength(self.Length); return new ReadOnlySpan(self.array, start, length); } + + /// + /// Removes the specified values from this list. + /// + /// The items to remove if matches are found in this list. + /// + /// The equality comparer to use in the search. + /// + /// + /// A new list with the elements removed. + /// + public ImmutableArray RemoveRange(ReadOnlySpan items, IEqualityComparer? equalityComparer = null) + { + var self = this; + self.ThrowNullRefIfNotInitialized(); + + if (items.IsEmpty || self.IsEmpty) + { + return self; + } + + if (items.Length == 1) + { + return self.Remove(items[0], equalityComparer); + } + +#nullable disable + var itemsMultiSet = new Dictionary(equalityComparer); +#nullable restore + int nullValueCount = 0; + foreach (ref readonly T item in items) + { + if (item == null) + { + nullValueCount++; + } + else + { +#nullable disable + ref int count = ref CollectionsMarshal.GetValueRefOrAddDefault(itemsMultiSet, item, out _); +#nullable restore + count++; + } + } + + List? indicesToRemove = null; + T[] selfArray = self.array!; + for (int i = 0; i < selfArray.Length; i++) + { + bool found = false; + if (selfArray[i] == null) + { + if (nullValueCount > 0) + { + found = true; + nullValueCount--; + } + } + else + { +#nullable disable + ref int count = ref CollectionsMarshal.GetValueRefOrNullRef(itemsMultiSet, selfArray[i]); +#nullable restore + if (!Unsafe.IsNullRef(ref count)) + { + found = true; + if (count == 1) + { + itemsMultiSet.Remove(selfArray[i]); + } + else + { + Debug.Assert(count > 1); + count--; + } + } + } + + if (found) + { + indicesToRemove ??= new(); + indicesToRemove.Add(i); + } + } + + return indicesToRemove == null ? self : self.RemoveAtRange(indicesToRemove); + } } } diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.nonnetcoreapp.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.nonnetcoreapp.cs new file mode 100644 index 0000000000000..2a66ce3cacd81 --- /dev/null +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.nonnetcoreapp.cs @@ -0,0 +1,93 @@ +// 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.Immutable +{ + public readonly partial struct ImmutableArray : IReadOnlyList, IList, IEquatable>, IList, IImmutableArray, IStructuralComparable, IStructuralEquatable, IImmutableList + { + /// + /// Removes the specified values from this list. + /// + /// The items to remove if matches are found in this list. + /// + /// The equality comparer to use in the search. + /// + /// + /// A new list with the elements removed. + /// + public ImmutableArray RemoveRange(ReadOnlySpan items, IEqualityComparer? equalityComparer = null) + { + var self = this; + self.ThrowNullRefIfNotInitialized(); + + if (items.IsEmpty || self.IsEmpty) + { + return self; + } + + if (items.Length == 1) + { + return self.Remove(items[0], equalityComparer); + } + +#nullable disable + var itemsMultiSet = new Dictionary(equalityComparer); +#nullable restore + int nullValueCount = 0; + foreach (ref readonly T item in items) + { + if (item == null) + { + nullValueCount++; + } + else if (itemsMultiSet.TryGetValue(item, out int count)) + { + itemsMultiSet[item] = count + 1; + } + else + { + itemsMultiSet[item] = 1; + } + } + + List? indicesToRemove = null; + T[] selfArray = self.array!; + for (int i = 0; i < selfArray.Length; i++) + { + bool found = false; + if (selfArray[i] == null) + { + if (nullValueCount > 0) + { + found = true; + nullValueCount--; + } + } + else if (itemsMultiSet.TryGetValue(selfArray[i], out int count)) + { + found = true; + if (count == 1) + { + itemsMultiSet.Remove(selfArray[i]); + } + else + { + Debug.Assert(count > 1); + itemsMultiSet[selfArray[i]] = count - 1; + } + } + + if (found) + { + indicesToRemove ??= new(); + indicesToRemove.Add(i); + } + } + + return indicesToRemove == null ? self : self.RemoveAtRange(indicesToRemove); + } + } +} From 7441ab9867250c6fac3bf79a5d887502a884b3f7 Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Wed, 26 Jan 2022 11:45:05 +0800 Subject: [PATCH 24/29] Fix comment: refine RemoveRange() to use 'continue'. --- .../Immutable/ImmutableArray_1.netcoreapp.cs | 28 ++++++------------- .../ImmutableArray_1.nonnetcoreapp.cs | 27 ++++++------------ 2 files changed, 17 insertions(+), 38 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.netcoreapp.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.netcoreapp.cs index ca50c8d4eb8ac..e582374d63bd1 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.netcoreapp.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.netcoreapp.cs @@ -72,40 +72,28 @@ public ImmutableArray RemoveRange(ReadOnlySpan items, IEqualityComparer T[] selfArray = self.array!; for (int i = 0; i < selfArray.Length; i++) { - bool found = false; if (selfArray[i] == null) { - if (nullValueCount > 0) + if (nullValueCount == 0) { - found = true; - nullValueCount--; + continue; } + nullValueCount--; } else { #nullable disable ref int count = ref CollectionsMarshal.GetValueRefOrNullRef(itemsMultiSet, selfArray[i]); #nullable restore - if (!Unsafe.IsNullRef(ref count)) + if (Unsafe.IsNullRef(ref count) || count == 0) { - found = true; - if (count == 1) - { - itemsMultiSet.Remove(selfArray[i]); - } - else - { - Debug.Assert(count > 1); - count--; - } + continue; } + count--; } - if (found) - { - indicesToRemove ??= new(); - indicesToRemove.Add(i); - } + indicesToRemove ??= new(); + indicesToRemove.Add(i); } return indicesToRemove == null ? self : self.RemoveAtRange(indicesToRemove); diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.nonnetcoreapp.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.nonnetcoreapp.cs index 2a66ce3cacd81..cfa8e926181b6 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.nonnetcoreapp.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.nonnetcoreapp.cs @@ -57,34 +57,25 @@ public ImmutableArray RemoveRange(ReadOnlySpan items, IEqualityComparer T[] selfArray = self.array!; for (int i = 0; i < selfArray.Length; i++) { - bool found = false; if (selfArray[i] == null) { - if (nullValueCount > 0) + if (nullValueCount == 0) { - found = true; - nullValueCount--; + continue; } + nullValueCount--; } - else if (itemsMultiSet.TryGetValue(selfArray[i], out int count)) + else { - found = true; - if (count == 1) - { - itemsMultiSet.Remove(selfArray[i]); - } - else + if (!itemsMultiSet.TryGetValue(selfArray[i], out int count) || count == 0) { - Debug.Assert(count > 1); - itemsMultiSet[selfArray[i]] = count - 1; + continue; } + itemsMultiSet[selfArray[i]] = count - 1; } - if (found) - { - indicesToRemove ??= new(); - indicesToRemove.Add(i); - } + indicesToRemove ??= new(); + indicesToRemove.Add(i); } return indicesToRemove == null ? self : self.RemoveAtRange(indicesToRemove); From f1d1d34f2ae47777741a216c9cb04322c5ed7763 Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Wed, 26 Jan 2022 11:50:19 +0800 Subject: [PATCH 25/29] Fix comment: merge 2 tfm version RemoveRange() back to ImmutableArray_1.cs. --- .../src/System.Collections.Immutable.csproj | 1 - .../Collections/Immutable/ImmutableArray_1.cs | 96 +++++++++++++++++++ .../Immutable/ImmutableArray_1.netcoreapp.cs | 78 --------------- .../ImmutableArray_1.nonnetcoreapp.cs | 84 ---------------- 4 files changed, 96 insertions(+), 163 deletions(-) delete mode 100644 src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.nonnetcoreapp.cs diff --git a/src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj b/src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj index 7348336c3baab..c66bec757e410 100644 --- a/src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj +++ b/src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj @@ -50,7 +50,6 @@ System.Collections.Immutable.ImmutableStack<T> - diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs index 3c3cd68e5c146..3e71e603d7c58 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs @@ -7,6 +7,8 @@ using System.Globalization; using System.Linq; using System.Runtime.Versioning; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace System.Collections.Immutable { @@ -856,6 +858,100 @@ public ImmutableArray InsertRange(int index, ReadOnlySpan items) return self.InsertSpanRangeInternal(index, items); } + /// + /// Removes the specified values from this list. + /// + /// The items to remove if matches are found in this list. + /// + /// The equality comparer to use in the search. + /// + /// + /// A new list with the elements removed. + /// + public ImmutableArray RemoveRange(ReadOnlySpan items, IEqualityComparer? equalityComparer = null) + { + var self = this; + self.ThrowNullRefIfNotInitialized(); + + if (items.IsEmpty || self.IsEmpty) + { + return self; + } + + if (items.Length == 1) + { + return self.Remove(items[0], equalityComparer); + } + +#nullable disable + var itemsMultiSet = new Dictionary(equalityComparer); +#nullable restore + int nullValueCount = 0; + foreach (ref readonly T item in items) + { + if (item == null) + { + nullValueCount++; + } + else + { +#if NET6_0_OR_GREATER +#nullable disable + ref int count = ref CollectionsMarshal.GetValueRefOrAddDefault(itemsMultiSet, item, out _); +#nullable restore + count++; +#else + if (itemsMultiSet.TryGetValue(item, out int count)) + { + itemsMultiSet[item] = count + 1; + } + else + { + itemsMultiSet[item] = 1; + } +#endif + } + } + + List? indicesToRemove = null; + T[] selfArray = self.array!; + for (int i = 0; i < selfArray.Length; i++) + { + if (selfArray[i] == null) + { + if (nullValueCount == 0) + { + continue; + } + nullValueCount--; + } + else + { +#if NET6_0_OR_GREATER +#nullable disable + ref int count = ref CollectionsMarshal.GetValueRefOrNullRef(itemsMultiSet, selfArray[i]); +#nullable restore + if (Unsafe.IsNullRef(ref count) || count == 0) + { + continue; + } + count--; +#else + if (!itemsMultiSet.TryGetValue(selfArray[i], out int count) || count == 0) + { + continue; + } + itemsMultiSet[selfArray[i]] = count - 1; +#endif + } + + indicesToRemove ??= new(); + indicesToRemove.Add(i); + } + + return indicesToRemove == null ? self : self.RemoveAtRange(indicesToRemove); + } + /// /// Removes the specified values from this list. /// diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.netcoreapp.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.netcoreapp.cs index e582374d63bd1..cf5c0013b7a31 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.netcoreapp.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.netcoreapp.cs @@ -2,9 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; namespace System.Collections.Immutable { @@ -23,80 +20,5 @@ public ReadOnlySpan AsSpan(Range range) (int start, int length) = range.GetOffsetAndLength(self.Length); return new ReadOnlySpan(self.array, start, length); } - - /// - /// Removes the specified values from this list. - /// - /// The items to remove if matches are found in this list. - /// - /// The equality comparer to use in the search. - /// - /// - /// A new list with the elements removed. - /// - public ImmutableArray RemoveRange(ReadOnlySpan items, IEqualityComparer? equalityComparer = null) - { - var self = this; - self.ThrowNullRefIfNotInitialized(); - - if (items.IsEmpty || self.IsEmpty) - { - return self; - } - - if (items.Length == 1) - { - return self.Remove(items[0], equalityComparer); - } - -#nullable disable - var itemsMultiSet = new Dictionary(equalityComparer); -#nullable restore - int nullValueCount = 0; - foreach (ref readonly T item in items) - { - if (item == null) - { - nullValueCount++; - } - else - { -#nullable disable - ref int count = ref CollectionsMarshal.GetValueRefOrAddDefault(itemsMultiSet, item, out _); -#nullable restore - count++; - } - } - - List? indicesToRemove = null; - T[] selfArray = self.array!; - for (int i = 0; i < selfArray.Length; i++) - { - if (selfArray[i] == null) - { - if (nullValueCount == 0) - { - continue; - } - nullValueCount--; - } - else - { -#nullable disable - ref int count = ref CollectionsMarshal.GetValueRefOrNullRef(itemsMultiSet, selfArray[i]); -#nullable restore - if (Unsafe.IsNullRef(ref count) || count == 0) - { - continue; - } - count--; - } - - indicesToRemove ??= new(); - indicesToRemove.Add(i); - } - - return indicesToRemove == null ? self : self.RemoveAtRange(indicesToRemove); - } } } diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.nonnetcoreapp.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.nonnetcoreapp.cs deleted file mode 100644 index cfa8e926181b6..0000000000000 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.nonnetcoreapp.cs +++ /dev/null @@ -1,84 +0,0 @@ -// 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.Immutable -{ - public readonly partial struct ImmutableArray : IReadOnlyList, IList, IEquatable>, IList, IImmutableArray, IStructuralComparable, IStructuralEquatable, IImmutableList - { - /// - /// Removes the specified values from this list. - /// - /// The items to remove if matches are found in this list. - /// - /// The equality comparer to use in the search. - /// - /// - /// A new list with the elements removed. - /// - public ImmutableArray RemoveRange(ReadOnlySpan items, IEqualityComparer? equalityComparer = null) - { - var self = this; - self.ThrowNullRefIfNotInitialized(); - - if (items.IsEmpty || self.IsEmpty) - { - return self; - } - - if (items.Length == 1) - { - return self.Remove(items[0], equalityComparer); - } - -#nullable disable - var itemsMultiSet = new Dictionary(equalityComparer); -#nullable restore - int nullValueCount = 0; - foreach (ref readonly T item in items) - { - if (item == null) - { - nullValueCount++; - } - else if (itemsMultiSet.TryGetValue(item, out int count)) - { - itemsMultiSet[item] = count + 1; - } - else - { - itemsMultiSet[item] = 1; - } - } - - List? indicesToRemove = null; - T[] selfArray = self.array!; - for (int i = 0; i < selfArray.Length; i++) - { - if (selfArray[i] == null) - { - if (nullValueCount == 0) - { - continue; - } - nullValueCount--; - } - else - { - if (!itemsMultiSet.TryGetValue(selfArray[i], out int count) || count == 0) - { - continue; - } - itemsMultiSet[selfArray[i]] = count - 1; - } - - indicesToRemove ??= new(); - indicesToRemove.Add(i); - } - - return indicesToRemove == null ? self : self.RemoveAtRange(indicesToRemove); - } - } -} From 5366414e7bf2c4ffb12e3d06ea4049799db80123 Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Sat, 29 Jan 2022 18:23:41 +0800 Subject: [PATCH 26/29] Add some demo-used test cases to indicate (null & custom equalityComparer) case issue for new RemoveRange(). --- .../tests/ImmutableArrayTest.cs | 140 ++++++++++++++---- 1 file changed, 111 insertions(+), 29 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs index bccc564288a6a..ba3ab3c697714 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs @@ -1475,57 +1475,126 @@ public void RemoveRangeIndexLengthDefaultInvalid(int index, int length) } [Theory] - [MemberData(nameof(RemoveRangeEnumerableData))] - public void RemoveRangeEnumerable(IEnumerable source, IEnumerable items, IEqualityComparer comparer) + [MemberData(nameof(RemoveRangeEnumerableNullData_WithAllEqualityComparer))] + public void OldRemoveRangeCanSupportNullByAnyEqualityComparer(IEnumerable source, IEnumerable items, IEqualityComparer comparer) { - ImmutableArray immutableArray = source.ToImmutableArray(); - IEnumerable expected = items.Aggregate( + ImmutableArray immutableArray = source.ToImmutableArray(); + IEnumerable expected = items.Aggregate( seed: source.ToImmutableArray(), func: (a, i) => a.Remove(i, comparer)); Assert.Equal(expected, immutableArray.RemoveRange(items, comparer)); // Enumerable overload + } + + [Theory] + [MemberData(nameof(RemoveRangeEnumerableNullData_WithDefaultEqualityComparer))] + public void NewRemoveRangeEnumerableCanSupportNullByDefaultEqualityComparer(IEnumerable source, IEnumerable items, IEqualityComparer comparer) + { + ImmutableArray immutableArray = source.ToImmutableArray(); + IEnumerable expected = items.Aggregate( + seed: source.ToImmutableArray(), + func: (a, i) => a.Remove(i, comparer)); + Assert.Equal(expected, immutableArray.RemoveRange(items.ToImmutableArray(), comparer)); // ImmutableArray overload - int[] array = items.ToArray(); + int?[] array = items.ToArray(); Assert.Equal(expected, immutableArray.RemoveRange(array, comparer)); // Array overload - ReadOnlySpan span = new ReadOnlySpan(array); + ReadOnlySpan span = new ReadOnlySpan(array); Assert.Equal(expected, immutableArray.RemoveRange(span, comparer)); // Span overload - Assert.Equal(expected, ((IImmutableList)immutableArray).RemoveRange(items, comparer)); + Assert.Equal(expected, ((IImmutableList)immutableArray).RemoveRange(items, comparer)); - if (comparer == null || comparer == EqualityComparer.Default) + if (comparer == null || comparer == EqualityComparer.Default) { - Assert.Equal(expected, immutableArray.RemoveRange(items)); // Enumerable overload Assert.Equal(expected, immutableArray.RemoveRange(items.ToImmutableArray())); // ImmutableArray overload Assert.Equal(expected, immutableArray.RemoveRange(array)); // Array overload Assert.Equal(expected, immutableArray.RemoveRange(span)); // Span overload - Assert.Equal(expected, ((IImmutableList)immutableArray).RemoveRange(items)); + Assert.Equal(expected, ((IImmutableList)immutableArray).RemoveRange(items)); + } + } + + [Theory] + [MemberData(nameof(RemoveRangeEnumerableNullData_WithStrangeCustomEqualityComparer))] + public void NewRemoveRangeEnumerableCannotSupportNullByStrangeCustomEqualityComparer(IEnumerable source, IEnumerable items, IEqualityComparer comparer) + { + ImmutableArray immutableArray = source.ToImmutableArray(); + IEnumerable expected = items.Aggregate( + seed: source.ToImmutableArray(), + func: (a, i) => a.Remove(i, comparer)); + + Assert.Equal(expected, immutableArray.RemoveRange(items.ToImmutableArray(), comparer)); // ImmutableArray overload + + int?[] array = items.ToArray(); + Assert.Equal(expected, immutableArray.RemoveRange(array, comparer)); // Array overload + ReadOnlySpan span = new ReadOnlySpan(array); + Assert.Equal(expected, immutableArray.RemoveRange(span, comparer)); // Span overload + + Assert.Equal(expected, ((IImmutableList)immutableArray).RemoveRange(items, comparer)); + + if (comparer == null || comparer == EqualityComparer.Default) + { + Assert.Equal(expected, immutableArray.RemoveRange(items.ToImmutableArray())); // ImmutableArray overload + Assert.Equal(expected, immutableArray.RemoveRange(array)); // Array overload + Assert.Equal(expected, immutableArray.RemoveRange(span)); // Span overload + Assert.Equal(expected, ((IImmutableList)immutableArray).RemoveRange(items)); } } public static IEnumerable RemoveRangeEnumerableData() { - return SharedEqualityComparers().SelectMany(comparer => + return SharedEqualityComparers().SelectMany(comparer => new[] { - new object[] { s_empty, s_empty, comparer }, - new object[] { s_empty, s_oneElement, comparer }, - new object[] { s_oneElement, s_empty, comparer }, - new object[] { new[] { 1, 2, 3 }, new[] { 2, 3, 4 }, comparer }, - new object[] { Enumerable.Range(1, 5), Enumerable.Range(6, 5), comparer }, - new object[] { new[] { 1, 2, 3 }, new[] { 2 }, comparer }, - new object[] { s_empty, new int[] { }, comparer }, - new object[] { new[] { 1, 2, 3 }, new[] { 2 }, comparer }, - new object[] { new[] { 1, 2, 3 }, new[] { 1, 3, 5 }, comparer }, - new object[] { Enumerable.Range(1, 10), new[] { 2, 4, 5, 7, 10 }, comparer }, - new object[] { Enumerable.Range(1, 10), new[] { 1, 2, 4, 5, 7, 10 }, comparer }, - new object[] { new[] { 1, 2, 3 }, new[] { 5 }, comparer }, - new object[] { new[] { 1, 2, 2, 3 }, new[] { 2 }, comparer }, - new object[] { new[] { 1, 2, 2, 3 }, new[] { 2, 2 }, comparer }, - new object[] { new[] { 1, 2, 2, 3 }, new[] { 2, 2, 2 }, comparer }, - new object[] { new[] { 1, 2, 3 }, new[] { 42 }, comparer }, - new object[] { new[] { 1, 2, 3 }, new[] { 42, 42 }, comparer }, - new object[] { new[] { 1, 2, 3 }, new[] { 42, 42, 42 }, comparer }, + new object[] { s_empty.Cast(), s_empty.Cast(), comparer }, + new object[] { s_empty.Cast(), s_oneElement.Cast(), comparer }, + new object[] { s_oneElement.Cast(), s_empty.Cast(), comparer }, + new object[] { new int?[] { 1, 2, 3 }, new int?[] { 2, 3, 4 }, comparer }, + new object[] { Enumerable.Range(1, 5).Cast(), Enumerable.Range(6, 5).Cast(), comparer }, + new object[] { new int?[] { 1, 2, 3 }, new int?[] { 2 }, comparer }, + new object[] { s_empty.Cast(), new int?[] { }, comparer }, + new object[] { new int?[] { 1, 2, 3 }, new int?[] { 2 }, comparer }, + new object[] { new int?[] { 1, 2, 3 }, new int?[] { 1, 3, 5 }, comparer }, + new object[] { Enumerable.Range(1, 10).Cast(), new int?[] { 2, 4, 5, 7, 10 }, comparer }, + new object[] { Enumerable.Range(1, 10).Cast(), new int?[] { 1, 2, 4, 5, 7, 10 }, comparer }, + new object[] { new int?[] { 1, 2, 3 }, new int?[] { 5 }, comparer }, + new object[] { new int?[] { 1, 2, 2, 3 }, new int?[] { 2 }, comparer }, + new object[] { new int?[] { 1, 2, 2, 3 }, new int?[] { 2, 2 }, comparer }, + new object[] { new int?[] { 1, 2, 2, 3 }, new int?[] { 2, 2, 2 }, comparer }, + new object[] { new int?[] { 1, 2, 3 }, new int?[] { 42 }, comparer }, + new object[] { new int?[] { 1, 2, 3 }, new int?[] { 42, 42 }, comparer }, + new object[] { new int?[] { 1, 2, 3 }, new int?[] { 42, 42, 42 }, comparer }, + new object[] { new int?[] { 1, 2 }, new int?[] { 1, null }, comparer }, + new object[] { new int?[] { 1, null }, new int?[] { 1 }, comparer }, + new object[] { new int?[] { 1, null, 2, null }, new int?[] { 1, null}, comparer }, + new object[] { new int?[] { 1, null, 2 }, new int?[] { 1, null, null}, comparer }, + new object[] { new int?[] { 1, null, 2, null }, new int?[] { 1, null, null}, comparer }, + }); + } + + public static IEnumerable RemoveRangeEnumerableNullData_WithAllEqualityComparer() + { + return SharedEqualityComparers().SelectMany(comparer => + new[] + { + new object[] { new int?[] { null, null }, new int?[] { null, 1 }, comparer } + }); + } + + public static IEnumerable RemoveRangeEnumerableNullData_WithDefaultEqualityComparer() + { + return DefaultEqualityComparers().SelectMany(comparer => + new[] + { + new object[] { new int?[] { null, null }, new int?[] { null, 1 }, comparer } + }); + } + + public static IEnumerable RemoveRangeEnumerableNullData_WithStrangeCustomEqualityComparer() + { + return AlwaysEqualsOrAlwaysNotEqualsEqualityComparers().SelectMany(comparer => + new[] + { + new object[] { new int?[] { null, null }, new int?[] { null, 1 }, comparer } }); } @@ -2624,6 +2693,19 @@ private static IEnumerable> SharedEqualityComparers() yield return new DelegateEqualityComparer(equals: (x, y) => false, objectGetHashCode: obj => 0, getHashCode: obj => 0); } + private static IEnumerable> DefaultEqualityComparers() + { + // Null comparers should be accepted and translated to the default comparer. + yield return null; + yield return EqualityComparer.Default; + } + + private static IEnumerable> AlwaysEqualsOrAlwaysNotEqualsEqualityComparers() + { + yield return new DelegateEqualityComparer(equals: (x, y) => true, objectGetHashCode: obj => 0, getHashCode: obj => 0); + yield return new DelegateEqualityComparer(equals: (x, y) => false, objectGetHashCode: obj => 0, getHashCode: obj => 0); + } + /// /// A structure that takes exactly 3 bytes of memory. /// From 1460a51af50856be65f0a2156c55d1f9e598842a Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Wed, 9 Feb 2022 11:15:33 +0800 Subject: [PATCH 27/29] Revert "Change RemoveRange(ReadOnlySpan) to be O(M+N) implementation." This reverts commit 50e278d7158e64b75e045ae098fac5f730c87091. --- .../Collections/Immutable/ImmutableArray_1.cs | 76 ++------- .../tests/ImmutableArrayTest.cs | 144 ++++-------------- 2 files changed, 40 insertions(+), 180 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs index 3e71e603d7c58..2ca3d661fa694 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs @@ -7,8 +7,6 @@ using System.Globalization; using System.Linq; using System.Runtime.Versioning; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; namespace System.Collections.Immutable { @@ -883,73 +881,17 @@ public ImmutableArray RemoveRange(ReadOnlySpan items, IEqualityComparer return self.Remove(items[0], equalityComparer); } -#nullable disable - var itemsMultiSet = new Dictionary(equalityComparer); -#nullable restore - int nullValueCount = 0; - foreach (ref readonly T item in items) - { - if (item == null) - { - nullValueCount++; - } - else - { -#if NET6_0_OR_GREATER -#nullable disable - ref int count = ref CollectionsMarshal.GetValueRefOrAddDefault(itemsMultiSet, item, out _); -#nullable restore - count++; -#else - if (itemsMultiSet.TryGetValue(item, out int count)) - { - itemsMultiSet[item] = count + 1; - } - else - { - itemsMultiSet[item] = 1; - } -#endif - } - } - - List? indicesToRemove = null; - T[] selfArray = self.array!; - for (int i = 0; i < selfArray.Length; i++) + var indicesToRemove = new SortedSet(); + foreach (var item in items) { - if (selfArray[i] == null) - { - if (nullValueCount == 0) - { - continue; - } - nullValueCount--; - } - else + int index = -1; + do { -#if NET6_0_OR_GREATER -#nullable disable - ref int count = ref CollectionsMarshal.GetValueRefOrNullRef(itemsMultiSet, selfArray[i]); -#nullable restore - if (Unsafe.IsNullRef(ref count) || count == 0) - { - continue; - } - count--; -#else - if (!itemsMultiSet.TryGetValue(selfArray[i], out int count) || count == 0) - { - continue; - } - itemsMultiSet[selfArray[i]] = count - 1; -#endif - } - - indicesToRemove ??= new(); - indicesToRemove.Add(i); + index = self.IndexOf(item, index + 1, equalityComparer); + } while (index >= 0 && !indicesToRemove.Add(index) && index < self.Length - 1); } - return indicesToRemove == null ? self : self.RemoveAtRange(indicesToRemove); + return self.RemoveAtRange(indicesToRemove); } /// @@ -985,7 +927,7 @@ public ImmutableArray Slice(int start, int length) return ImmutableArray.Create(self, start, length); } -#region Explicit interface methods + #region Explicit interface methods void IList.Insert(int index, T item) { @@ -1398,7 +1340,7 @@ int IStructuralComparable.CompareTo(object? other, IComparer comparer) throw new ArgumentException(SR.ArrayLengthsNotEqual, nameof(other)); } -#endregion + #endregion /// diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs index ba3ab3c697714..76e42ad0a8e8a 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs @@ -1475,126 +1475,57 @@ public void RemoveRangeIndexLengthDefaultInvalid(int index, int length) } [Theory] - [MemberData(nameof(RemoveRangeEnumerableNullData_WithAllEqualityComparer))] - public void OldRemoveRangeCanSupportNullByAnyEqualityComparer(IEnumerable source, IEnumerable items, IEqualityComparer comparer) + [MemberData(nameof(RemoveRangeEnumerableData))] + public void RemoveRangeEnumerable(IEnumerable source, IEnumerable items, IEqualityComparer comparer) { - ImmutableArray immutableArray = source.ToImmutableArray(); - IEnumerable expected = items.Aggregate( + ImmutableArray immutableArray = source.ToImmutableArray(); + IEnumerable expected = items.Aggregate( seed: source.ToImmutableArray(), func: (a, i) => a.Remove(i, comparer)); Assert.Equal(expected, immutableArray.RemoveRange(items, comparer)); // Enumerable overload - } - - [Theory] - [MemberData(nameof(RemoveRangeEnumerableNullData_WithDefaultEqualityComparer))] - public void NewRemoveRangeEnumerableCanSupportNullByDefaultEqualityComparer(IEnumerable source, IEnumerable items, IEqualityComparer comparer) - { - ImmutableArray immutableArray = source.ToImmutableArray(); - IEnumerable expected = items.Aggregate( - seed: source.ToImmutableArray(), - func: (a, i) => a.Remove(i, comparer)); - - Assert.Equal(expected, immutableArray.RemoveRange(items.ToImmutableArray(), comparer)); // ImmutableArray overload - - int?[] array = items.ToArray(); - Assert.Equal(expected, immutableArray.RemoveRange(array, comparer)); // Array overload - ReadOnlySpan span = new ReadOnlySpan(array); - Assert.Equal(expected, immutableArray.RemoveRange(span, comparer)); // Span overload - - Assert.Equal(expected, ((IImmutableList)immutableArray).RemoveRange(items, comparer)); - - if (comparer == null || comparer == EqualityComparer.Default) - { - Assert.Equal(expected, immutableArray.RemoveRange(items.ToImmutableArray())); // ImmutableArray overload - Assert.Equal(expected, immutableArray.RemoveRange(array)); // Array overload - Assert.Equal(expected, immutableArray.RemoveRange(span)); // Span overload - Assert.Equal(expected, ((IImmutableList)immutableArray).RemoveRange(items)); - } - } - - [Theory] - [MemberData(nameof(RemoveRangeEnumerableNullData_WithStrangeCustomEqualityComparer))] - public void NewRemoveRangeEnumerableCannotSupportNullByStrangeCustomEqualityComparer(IEnumerable source, IEnumerable items, IEqualityComparer comparer) - { - ImmutableArray immutableArray = source.ToImmutableArray(); - IEnumerable expected = items.Aggregate( - seed: source.ToImmutableArray(), - func: (a, i) => a.Remove(i, comparer)); - Assert.Equal(expected, immutableArray.RemoveRange(items.ToImmutableArray(), comparer)); // ImmutableArray overload - int?[] array = items.ToArray(); + int[] array = items.ToArray(); Assert.Equal(expected, immutableArray.RemoveRange(array, comparer)); // Array overload - ReadOnlySpan span = new ReadOnlySpan(array); + ReadOnlySpan span = new ReadOnlySpan(array); Assert.Equal(expected, immutableArray.RemoveRange(span, comparer)); // Span overload - Assert.Equal(expected, ((IImmutableList)immutableArray).RemoveRange(items, comparer)); + Assert.Equal(expected, ((IImmutableList)immutableArray).RemoveRange(items, comparer)); - if (comparer == null || comparer == EqualityComparer.Default) + if (comparer == null || comparer == EqualityComparer.Default) { + Assert.Equal(expected, immutableArray.RemoveRange(items)); // Enumerable overload Assert.Equal(expected, immutableArray.RemoveRange(items.ToImmutableArray())); // ImmutableArray overload Assert.Equal(expected, immutableArray.RemoveRange(array)); // Array overload Assert.Equal(expected, immutableArray.RemoveRange(span)); // Span overload - Assert.Equal(expected, ((IImmutableList)immutableArray).RemoveRange(items)); + Assert.Equal(expected, ((IImmutableList)immutableArray).RemoveRange(items)); } } public static IEnumerable RemoveRangeEnumerableData() { - return SharedEqualityComparers().SelectMany(comparer => - new[] - { - new object[] { s_empty.Cast(), s_empty.Cast(), comparer }, - new object[] { s_empty.Cast(), s_oneElement.Cast(), comparer }, - new object[] { s_oneElement.Cast(), s_empty.Cast(), comparer }, - new object[] { new int?[] { 1, 2, 3 }, new int?[] { 2, 3, 4 }, comparer }, - new object[] { Enumerable.Range(1, 5).Cast(), Enumerable.Range(6, 5).Cast(), comparer }, - new object[] { new int?[] { 1, 2, 3 }, new int?[] { 2 }, comparer }, - new object[] { s_empty.Cast(), new int?[] { }, comparer }, - new object[] { new int?[] { 1, 2, 3 }, new int?[] { 2 }, comparer }, - new object[] { new int?[] { 1, 2, 3 }, new int?[] { 1, 3, 5 }, comparer }, - new object[] { Enumerable.Range(1, 10).Cast(), new int?[] { 2, 4, 5, 7, 10 }, comparer }, - new object[] { Enumerable.Range(1, 10).Cast(), new int?[] { 1, 2, 4, 5, 7, 10 }, comparer }, - new object[] { new int?[] { 1, 2, 3 }, new int?[] { 5 }, comparer }, - new object[] { new int?[] { 1, 2, 2, 3 }, new int?[] { 2 }, comparer }, - new object[] { new int?[] { 1, 2, 2, 3 }, new int?[] { 2, 2 }, comparer }, - new object[] { new int?[] { 1, 2, 2, 3 }, new int?[] { 2, 2, 2 }, comparer }, - new object[] { new int?[] { 1, 2, 3 }, new int?[] { 42 }, comparer }, - new object[] { new int?[] { 1, 2, 3 }, new int?[] { 42, 42 }, comparer }, - new object[] { new int?[] { 1, 2, 3 }, new int?[] { 42, 42, 42 }, comparer }, - new object[] { new int?[] { 1, 2 }, new int?[] { 1, null }, comparer }, - new object[] { new int?[] { 1, null }, new int?[] { 1 }, comparer }, - new object[] { new int?[] { 1, null, 2, null }, new int?[] { 1, null}, comparer }, - new object[] { new int?[] { 1, null, 2 }, new int?[] { 1, null, null}, comparer }, - new object[] { new int?[] { 1, null, 2, null }, new int?[] { 1, null, null}, comparer }, - }); - } - - public static IEnumerable RemoveRangeEnumerableNullData_WithAllEqualityComparer() - { - return SharedEqualityComparers().SelectMany(comparer => - new[] - { - new object[] { new int?[] { null, null }, new int?[] { null, 1 }, comparer } - }); - } - - public static IEnumerable RemoveRangeEnumerableNullData_WithDefaultEqualityComparer() - { - return DefaultEqualityComparers().SelectMany(comparer => - new[] - { - new object[] { new int?[] { null, null }, new int?[] { null, 1 }, comparer } - }); - } - - public static IEnumerable RemoveRangeEnumerableNullData_WithStrangeCustomEqualityComparer() - { - return AlwaysEqualsOrAlwaysNotEqualsEqualityComparers().SelectMany(comparer => + return SharedEqualityComparers().SelectMany(comparer => new[] { - new object[] { new int?[] { null, null }, new int?[] { null, 1 }, comparer } + new object[] { s_empty, s_empty, comparer }, + new object[] { s_empty, s_oneElement, comparer }, + new object[] { s_oneElement, s_empty, comparer }, + new object[] { new[] { 1, 2, 3 }, new[] { 2, 3, 4 }, comparer }, + new object[] { Enumerable.Range(1, 5), Enumerable.Range(6, 5), comparer }, + new object[] { new[] { 1, 2, 3 }, new[] { 2 }, comparer }, + new object[] { s_empty, new int[] { }, comparer }, + new object[] { new[] { 1, 2, 3 }, new[] { 2 }, comparer }, + new object[] { new[] { 1, 2, 3 }, new[] { 1, 3, 5 }, comparer }, + new object[] { Enumerable.Range(1, 10), new[] { 2, 4, 5, 7, 10 }, comparer }, + new object[] { Enumerable.Range(1, 10), new[] { 1, 2, 4, 5, 7, 10 }, comparer }, + new object[] { new[] { 1, 2, 3 }, new[] { 5 }, comparer }, + new object[] { new[] { 1, 2, 2, 3 }, new[] { 2 }, comparer }, + new object[] { new[] { 1, 2, 2, 3 }, new[] { 2, 2 }, comparer }, + new object[] { new[] { 1, 2, 2, 3 }, new[] { 2, 2, 2 }, comparer }, + new object[] { new[] { 1, 2, 3 }, new[] { 42 }, comparer }, + new object[] { new[] { 1, 2, 3 }, new[] { 42, 42 }, comparer }, + new object[] { new[] { 1, 2, 3 }, new[] { 42, 42, 42 }, comparer }, }); } @@ -2689,21 +2620,8 @@ private static IEnumerable> SharedEqualityComparers() // Null comparers should be accepted and translated to the default comparer. yield return null; yield return EqualityComparer.Default; - yield return new DelegateEqualityComparer(equals: (x, y) => true, objectGetHashCode: obj => 0, getHashCode: obj => 0); - yield return new DelegateEqualityComparer(equals: (x, y) => false, objectGetHashCode: obj => 0, getHashCode: obj => 0); - } - - private static IEnumerable> DefaultEqualityComparers() - { - // Null comparers should be accepted and translated to the default comparer. - yield return null; - yield return EqualityComparer.Default; - } - - private static IEnumerable> AlwaysEqualsOrAlwaysNotEqualsEqualityComparers() - { - yield return new DelegateEqualityComparer(equals: (x, y) => true, objectGetHashCode: obj => 0, getHashCode: obj => 0); - yield return new DelegateEqualityComparer(equals: (x, y) => false, objectGetHashCode: obj => 0, getHashCode: obj => 0); + yield return new DelegateEqualityComparer(equals: (x, y) => true, objectGetHashCode: obj => 0); + yield return new DelegateEqualityComparer(equals: (x, y) => false, objectGetHashCode: obj => 0); } /// From 85ebef429a0d33344e188bd18f47c1122a1bbb7d Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Thu, 10 Feb 2022 14:56:56 +0800 Subject: [PATCH 28/29] Fix comment: use explicit type name; enhance RemoveRange test cases to cover 'null' case. --- .../Collections/Immutable/ImmutableArray_1.cs | 4 +- .../tests/ImmutableArrayTest.cs | 55 ++++++++++--------- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs index 2ca3d661fa694..a49db938efe0c 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs @@ -576,7 +576,7 @@ public ImmutableArray RemoveRange(IEnumerable items, IEqualityComparer? Requires.NotNull(items, nameof(items)); var indicesToRemove = new SortedSet(); - foreach (var item in items) + foreach (T item in items) { int index = -1; do @@ -882,7 +882,7 @@ public ImmutableArray RemoveRange(ReadOnlySpan items, IEqualityComparer } var indicesToRemove = new SortedSet(); - foreach (var item in items) + foreach (T item in items) { int index = -1; do diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs index 76e42ad0a8e8a..3193a996faf5c 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs @@ -1476,22 +1476,22 @@ public void RemoveRangeIndexLengthDefaultInvalid(int index, int length) [Theory] [MemberData(nameof(RemoveRangeEnumerableData))] - public void RemoveRangeEnumerable(IEnumerable source, IEnumerable items, IEqualityComparer comparer) + public void RemoveRangeEnumerable(IEnumerable source, IEnumerable items, IEqualityComparer comparer) { - ImmutableArray immutableArray = source.ToImmutableArray(); - IEnumerable expected = items.Aggregate( + ImmutableArray immutableArray = source.ToImmutableArray(); + IEnumerable expected = items.Aggregate( seed: source.ToImmutableArray(), func: (a, i) => a.Remove(i, comparer)); Assert.Equal(expected, immutableArray.RemoveRange(items, comparer)); // Enumerable overload Assert.Equal(expected, immutableArray.RemoveRange(items.ToImmutableArray(), comparer)); // ImmutableArray overload - int[] array = items.ToArray(); + int?[] array = items.ToArray(); Assert.Equal(expected, immutableArray.RemoveRange(array, comparer)); // Array overload - ReadOnlySpan span = new ReadOnlySpan(array); + ReadOnlySpan span = new ReadOnlySpan(array); Assert.Equal(expected, immutableArray.RemoveRange(span, comparer)); // Span overload - Assert.Equal(expected, ((IImmutableList)immutableArray).RemoveRange(items, comparer)); + Assert.Equal(expected, ((IImmutableList)immutableArray).RemoveRange(items, comparer)); if (comparer == null || comparer == EqualityComparer.Default) { @@ -1499,33 +1499,36 @@ public void RemoveRangeEnumerable(IEnumerable source, IEnumerable item Assert.Equal(expected, immutableArray.RemoveRange(items.ToImmutableArray())); // ImmutableArray overload Assert.Equal(expected, immutableArray.RemoveRange(array)); // Array overload Assert.Equal(expected, immutableArray.RemoveRange(span)); // Span overload - Assert.Equal(expected, ((IImmutableList)immutableArray).RemoveRange(items)); + Assert.Equal(expected, ((IImmutableList)immutableArray).RemoveRange(items)); } } public static IEnumerable RemoveRangeEnumerableData() { - return SharedEqualityComparers().SelectMany(comparer => + return SharedEqualityComparers().SelectMany(comparer => new[] { - new object[] { s_empty, s_empty, comparer }, - new object[] { s_empty, s_oneElement, comparer }, - new object[] { s_oneElement, s_empty, comparer }, - new object[] { new[] { 1, 2, 3 }, new[] { 2, 3, 4 }, comparer }, - new object[] { Enumerable.Range(1, 5), Enumerable.Range(6, 5), comparer }, - new object[] { new[] { 1, 2, 3 }, new[] { 2 }, comparer }, - new object[] { s_empty, new int[] { }, comparer }, - new object[] { new[] { 1, 2, 3 }, new[] { 2 }, comparer }, - new object[] { new[] { 1, 2, 3 }, new[] { 1, 3, 5 }, comparer }, - new object[] { Enumerable.Range(1, 10), new[] { 2, 4, 5, 7, 10 }, comparer }, - new object[] { Enumerable.Range(1, 10), new[] { 1, 2, 4, 5, 7, 10 }, comparer }, - new object[] { new[] { 1, 2, 3 }, new[] { 5 }, comparer }, - new object[] { new[] { 1, 2, 2, 3 }, new[] { 2 }, comparer }, - new object[] { new[] { 1, 2, 2, 3 }, new[] { 2, 2 }, comparer }, - new object[] { new[] { 1, 2, 2, 3 }, new[] { 2, 2, 2 }, comparer }, - new object[] { new[] { 1, 2, 3 }, new[] { 42 }, comparer }, - new object[] { new[] { 1, 2, 3 }, new[] { 42, 42 }, comparer }, - new object[] { new[] { 1, 2, 3 }, new[] { 42, 42, 42 }, comparer }, + new object[] { Array.Empty(), Array.Empty(), comparer }, + new object[] { Array.Empty(), new int?[] { 1 }, comparer }, + new object[] { new int?[] { 1 }, Array.Empty(), comparer }, + new object[] { new int?[] { 1, 2, 3 }, new int?[] { 2, 3, 4 }, comparer }, + new object[] { Enumerable.Range(1, 5).Cast(), Enumerable.Range(6, 5).Cast(), comparer }, + new object[] { new int?[] { 1, 2, 3 }, new int?[] { 2 }, comparer }, + new object[] { new int?[] { 1, 2, 3 }, new int?[] { 1, 3, 5 }, comparer }, + new object[] { Enumerable.Range(1, 10).Cast(), new int?[] { 2, 4, 5, 7, 10 }, comparer }, + new object[] { Enumerable.Range(1, 10).Cast(), new int?[] { 1, 2, 4, 5, 7, 10 }, comparer }, + new object[] { new int?[] { 1, 2, 3 }, new int?[] { 5 }, comparer }, + new object[] { new int?[] { 1, 2, 2, 3 }, new int?[] { 2 }, comparer }, + new object[] { new int?[] { 1, 2, 2, 3 }, new int?[] { 2, 2 }, comparer }, + new object[] { new int?[] { 1, 2, 2, 3 }, new int?[] { 2, 2, 2 }, comparer }, + new object[] { new int?[] { 1, 2, 3 }, new int?[] { 42 }, comparer }, + new object[] { new int?[] { 1, 2, 3 }, new int?[] { 42, 42 }, comparer }, + new object[] { new int?[] { 1, 2, 3 }, new int?[] { 42, 42, 42 }, comparer }, + new object[] { new int?[] { null }, new int?[] { 1 }, comparer }, + new object[] { new int?[] { 1 }, new int?[] { null}, comparer }, + new object[] { new int?[] { 1, null, 2, null }, new int?[] { 1, null}, comparer }, + new object[] { new int?[] { 1, null, 2 }, new int?[] { 1, null, null}, comparer }, + new object[] { new int?[] { 1, null, 2, null }, new int?[] { 1, null, null}, comparer }, }); } From 1bb557c9a1c30da7f90c63045a8c7ef3597bd046 Mon Sep 17 00:00:00 2001 From: lateapexearlyspeed Date: Thu, 10 Feb 2022 15:02:34 +0800 Subject: [PATCH 29/29] Use explicit type names. --- .../src/System/Collections/Immutable/ImmutableArray_1.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs index a49db938efe0c..8b7ebb92f9524 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs @@ -356,7 +356,7 @@ public ImmutableArray InsertRange(int index, IEnumerable items) if (!items.TryCopyTo(tmp, index)) { int sequenceIndex = index; - foreach (var item in items) + foreach (T item in items) { tmp[sequenceIndex++] = item; } @@ -1364,7 +1364,7 @@ private ImmutableArray RemoveAtRange(ICollection indicesToRemove) int copied = 0; int removed = 0; int lastIndexRemoved = -1; - foreach (var indexToRemove in indicesToRemove) + foreach (int indexToRemove in indicesToRemove) { int copyLength = lastIndexRemoved == -1 ? indexToRemove : (indexToRemove - lastIndexRemoved - 1); Debug.Assert(indexToRemove > lastIndexRemoved); // We require that the input be a sorted set.