From 55fbf93fc2396e13f6977c24e56505a41af30c3f Mon Sep 17 00:00:00 2001 From: DaZombieKiller Date: Thu, 27 May 2021 01:15:19 +1000 Subject: [PATCH 1/8] Expose public indexer and length properties on RefEnumerable and ReadOnlyRefEnumerable --- .../Enumerables/ReadOnlyRefEnumerable{T}.cs | 149 ++++++++++------ .../Enumerables/RefEnumerable{T}.cs | 163 ++++++++++++------ 2 files changed, 207 insertions(+), 105 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs index ad5fc7e3519..65be73fe452 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs @@ -29,6 +29,11 @@ public readonly ref struct ReadOnlyRefEnumerable /// /// The field maps to the total available length. private readonly ReadOnlySpan span; + + /// + /// Gets the total available length for the sequence. + /// + public int Length => span.Length; #else /// /// The target instance, if present. @@ -41,9 +46,9 @@ public readonly ref struct ReadOnlyRefEnumerable private readonly IntPtr offset; /// - /// The total available length for the sequence. + /// Gets the total available length for the sequence. /// - private readonly int length; + public int Length { get; } #endif /// @@ -52,6 +57,40 @@ public readonly ref struct ReadOnlyRefEnumerable /// The distance refers to items, not byte offset. private readonly int step; + /// + /// Gets the element at the specified zero-based index. + /// + /// A reference to the element at the specified index. + /// + /// Thrown when is invalid. + /// + public ref readonly T this[int index] + { + get + { + if ((uint)index >= (uint)Length) + { + ThrowHelper.ThrowIndexOutOfRangeException(); + } + + return ref DangerousGetReferenceAt(index); + } + } + +#if NETSTANDARD2_1_OR_GREATER + /// + /// Gets the element at the specified zero-based index. + /// + /// A reference to the element at the specified index. + /// + /// Thrown when is invalid. + /// + public ref readonly T this[Index index] + { + get => ref this[index.GetOffset(Length)]; + } +#endif + #if SPAN_RUNTIME_SUPPORT /// /// Initializes a new instance of the struct. @@ -116,21 +155,52 @@ internal ReadOnlyRefEnumerable(object? instance, IntPtr offset, int length, int { this.instance = instance; this.offset = offset; - this.length = length; + Length = length; this.step = step; } #endif + /// + /// Returns a reference to the first element within the current instance, with no bounds check. + /// + /// A reference to the first element within the current instance. + internal ref readonly T DangerousGetReference() + { +#if SPAN_RUNTIME_SUPPORT + return ref MemoryMarshal.GetReference(this.span); +#else + return ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); +#endif + } + + /// + /// Returns a reference to a specified element within the current instance, with no bounds check. + /// + /// A reference to the element at the specified index. + internal ref readonly T DangerousGetReferenceAt(int index) + { +#if SPAN_RUNTIME_SUPPORT + ref T r0 = ref MemoryMarshal.GetReference(this.span); +#else + ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); +#endif + // Here we just offset by shifting down as if we were traversing a 2D array with a + // a single column, with the width of each row represented by the step, the height + // represented by the current position, and with only the first element of each row + // being inspected. We can perform all the indexing operations in this type as nint, + // as the maximum offset is guaranteed never to exceed the maximum value, since on + // 32 bit architectures it's not possible to allocate that much memory anyway. + nint offset = (nint)(uint)index * (nint)(uint)this.step; + ref T ri = ref Unsafe.Add(ref r0, offset); + return ref ri; + } + /// [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public Enumerator GetEnumerator() { -#if SPAN_RUNTIME_SUPPORT - return new Enumerator(this.span, this.step); -#else - return new Enumerator(this.instance, this.offset, this.length, this.step); -#endif + return new Enumerator(this); } /// @@ -166,7 +236,7 @@ public void CopyTo(RefEnumerable destination) ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); ref T destinationRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(destination.Instance, destination.Offset); int - sourceLength = this.length, + sourceLength = Length, destinationLength = destination.Length; #endif @@ -191,7 +261,7 @@ public bool TryCopyTo(RefEnumerable destination) destinationLength = destination.Span.Length; #else int - sourceLength = this.length, + sourceLength = Length, destinationLength = destination.Length; #endif @@ -226,7 +296,7 @@ public void CopyTo(Span destination) int length = this.span.Length; #else ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); - int length = this.length; + int length = Length; #endif if ((uint)destination.Length < (uint)length) { @@ -248,7 +318,7 @@ public bool TryCopyTo(Span destination) #if SPAN_RUNTIME_SUPPORT int length = this.span.Length; #else - int length = this.length; + int length = Length; #endif if (destination.Length >= length) @@ -268,7 +338,7 @@ public T[] ToArray() #if SPAN_RUNTIME_SUPPORT int length = this.span.Length; #else - int length = this.length; + int length = Length; #endif // Empty array if no data is mapped @@ -303,22 +373,10 @@ public static implicit operator ReadOnlyRefEnumerable(RefEnumerable enumer /// public ref struct Enumerator { -#if SPAN_RUNTIME_SUPPORT - /// - private readonly ReadOnlySpan span; -#else - /// - private readonly object? instance; - - /// - private readonly IntPtr offset; - - /// - private readonly int length; -#endif - - /// - private readonly int step; + /// + /// The used by this enumerator. + /// + private readonly ReadOnlyRefEnumerable enumerable; /// /// The current position in the sequence. @@ -333,10 +391,8 @@ public ref struct Enumerator /// The distance between items in the sequence to enumerate. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Enumerator(ReadOnlySpan span, int step) + : this(new ReadOnlyRefEnumerable(span, step)) { - this.span = span; - this.step = step; - this.position = -1; } #else /// @@ -348,23 +404,25 @@ internal Enumerator(ReadOnlySpan span, int step) /// The distance between items in the sequence to enumerate. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Enumerator(object? instance, IntPtr offset, int length, int step) + : this(new ReadOnlyRefEnumerable(instance, offset, length, step)) { - this.instance = instance; - this.offset = offset; - this.length = length; - this.step = step; - this.position = -1; } #endif + internal Enumerator(ReadOnlyRefEnumerable enumerable) + { + this.enumerable = enumerable; + this.position = -1; + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool MoveNext() { #if SPAN_RUNTIME_SUPPORT - return ++this.position < this.span.Length; + return ++this.position < this.enumerable.span.Length; #else - return ++this.position < this.length; + return ++this.position < this.enumerable.Length; #endif } @@ -372,18 +430,7 @@ public bool MoveNext() public readonly ref readonly T Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { -#if SPAN_RUNTIME_SUPPORT - ref T r0 = ref this.span.DangerousGetReference(); -#else - ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); -#endif - nint offset = (nint)(uint)this.position * (nint)(uint)this.step; - ref T ri = ref Unsafe.Add(ref r0, offset); - - return ref ri; - } + get => ref this.enumerable.DangerousGetReferenceAt(this.position); } } diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs index 3ceee9311a9..6ce3e679a3f 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs @@ -9,9 +9,9 @@ using System.Runtime.InteropServices; #endif using Microsoft.Toolkit.HighPerformance.Helpers.Internals; -#if SPAN_RUNTIME_SUPPORT using Microsoft.Toolkit.HighPerformance.Memory.Internals; -#else + +#if !SPAN_RUNTIME_SUPPORT using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers; #endif @@ -29,6 +29,11 @@ public readonly ref struct RefEnumerable /// /// The field maps to the total available length. internal readonly Span Span; + + /// + /// Gets the total available length for the sequence. + /// + public int Length => Span.Length; #else /// /// The target instance, if present. @@ -41,9 +46,9 @@ public readonly ref struct RefEnumerable internal readonly IntPtr Offset; /// - /// The total available length for the sequence. + /// Gets the total available length for the sequence. /// - internal readonly int Length; + public int Length { get; } #endif /// @@ -52,6 +57,40 @@ public readonly ref struct RefEnumerable /// The distance refers to items, not byte offset. internal readonly int Step; + /// + /// Gets the element at the specified zero-based index. + /// + /// A reference to the element at the specified index. + /// + /// Thrown when is invalid. + /// + public ref T this[int index] + { + get + { + if ((uint)index >= (uint)Length) + { + ThrowHelper.ThrowIndexOutOfRangeException(); + } + + return ref DangerousGetReferenceAt(index); + } + } + +#if NETSTANDARD2_1_OR_GREATER + /// + /// Gets the element at the specified zero-based index. + /// + /// A reference to the element at the specified index. + /// + /// Thrown when is invalid. + /// + public ref T this[Index index] + { + get => ref this[index.GetOffset(Length)]; + } +#endif + #if SPAN_RUNTIME_SUPPORT /// /// Initializes a new instance of the struct. @@ -66,6 +105,18 @@ internal RefEnumerable(ref T reference, int length, int step) Step = step; } + /// + /// Initializes a new instance of the struct. + /// + /// The instance pointing to the first item in the target memory area. + /// The distance between items in the sequence to enumerate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal RefEnumerable(Span span, int step) + { + Span = span; + Step = step; + } + /// /// Creates a new instance of the struct with the specified parameters. /// @@ -109,16 +160,47 @@ internal RefEnumerable(object? instance, IntPtr offset, int length, int step) } #endif + /// + /// Returns a reference to the first element within the current instance, with no bounds check. + /// + /// A reference to the first element within the current instance. + internal ref T DangerousGetReference() + { +#if SPAN_RUNTIME_SUPPORT + return ref MemoryMarshal.GetReference(this.Span); +#else + return ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset); +#endif + } + + /// + /// Returns a reference to a specified element within the current instance, with no bounds check. + /// + /// A reference to the element at the specified index. + internal ref T DangerousGetReferenceAt(int index) + { +#if SPAN_RUNTIME_SUPPORT + ref T r0 = ref MemoryMarshal.GetReference(this.Span); +#else + ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset); +#endif + // Here we just offset by shifting down as if we were traversing a 2D array with a + // a single column, with the width of each row represented by the step, the height + // represented by the current position, and with only the first element of each row + // being inspected. We can perform all the indexing operations in this type as nint, + // as the maximum offset is guaranteed never to exceed the maximum value, since on + // 32 bit architectures it's not possible to allocate that much memory anyway. + nint offset = (nint)(uint)index * (nint)(uint)this.Step; + ref T ri = ref Unsafe.Add(ref r0, offset); + return ref ri; + } + /// [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public Enumerator GetEnumerator() { -#if SPAN_RUNTIME_SUPPORT - return new Enumerator(this.Span, this.Step); -#else - return new Enumerator(this.Instance, this.Offset, this.Length, this.Step); -#endif + return new Enumerator(this); } /// @@ -389,22 +471,10 @@ public T[] ToArray() /// public ref struct Enumerator { -#if SPAN_RUNTIME_SUPPORT - /// - private readonly Span span; -#else - /// - private readonly object? instance; - - /// - private readonly IntPtr offset; - - /// - private readonly int length; -#endif - - /// - private readonly int step; + /// + /// The used by this enumerator. + /// + private readonly RefEnumerable enumerable; /// /// The current position in the sequence. @@ -419,10 +489,8 @@ public ref struct Enumerator /// The distance between items in the sequence to enumerate. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Enumerator(Span span, int step) + : this(new RefEnumerable(span, step)) { - this.span = span; - this.step = step; - this.position = -1; } #else /// @@ -434,23 +502,28 @@ internal Enumerator(Span span, int step) /// The distance between items in the sequence to enumerate. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Enumerator(object? instance, IntPtr offset, int length, int step) + : this(new RefEnumerable(instance, offset, length, step)) { - this.instance = instance; - this.offset = offset; - this.length = length; - this.step = step; - this.position = -1; } #endif + /// + /// Initializes a new instance of the struct. + /// + internal Enumerator(RefEnumerable enumerable) + { + this.enumerable = enumerable; + this.position = -1; + } + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool MoveNext() { #if SPAN_RUNTIME_SUPPORT - return ++this.position < this.span.Length; + return ++this.position < this.enumerable.Span.Length; #else - return ++this.position < this.length; + return ++this.position < this.enumerable.Length; #endif } @@ -458,25 +531,7 @@ public bool MoveNext() public readonly ref T Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { -#if SPAN_RUNTIME_SUPPORT - ref T r0 = ref this.span.DangerousGetReference(); -#else - ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); -#endif - - // Here we just offset by shifting down as if we were traversing a 2D array with a - // a single column, with the width of each row represented by the step, the height - // represented by the current position, and with only the first element of each row - // being inspected. We can perform all the indexing operations in this type as nint, - // as the maximum offset is guaranteed never to exceed the maximum value, since on - // 32 bit architectures it's not possible to allocate that much memory anyway. - nint offset = (nint)(uint)this.position * (nint)(uint)this.step; - ref T ri = ref Unsafe.Add(ref r0, offset); - - return ref ri; - } + get => ref this.enumerable.DangerousGetReferenceAt(this.position); } } From 0152072564f88b1bdc96c127f8b88ce6a695a2b8 Mon Sep 17 00:00:00 2001 From: DaZombieKiller Date: Thu, 27 May 2021 20:38:13 +1000 Subject: [PATCH 2/8] Add newline before comments in DangerousGetReferenceAt --- .../Enumerables/ReadOnlyRefEnumerable{T}.cs | 1 + .../Enumerables/RefEnumerable{T}.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs index 65be73fe452..831f95f7f1f 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs @@ -184,6 +184,7 @@ internal ref readonly T DangerousGetReferenceAt(int index) #else ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); #endif + // Here we just offset by shifting down as if we were traversing a 2D array with a // a single column, with the width of each row represented by the step, the height // represented by the current position, and with only the first element of each row diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs index 6ce3e679a3f..70fe1521239 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs @@ -184,6 +184,7 @@ internal ref T DangerousGetReferenceAt(int index) #else ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset); #endif + // Here we just offset by shifting down as if we were traversing a 2D array with a // a single column, with the width of each row represented by the step, the height // represented by the current position, and with only the first element of each row From 86625a097f6b9014225a3d681a4c1b219024c422 Mon Sep 17 00:00:00 2001 From: DaZombieKiller Date: Fri, 28 May 2021 03:31:18 +1000 Subject: [PATCH 3/8] Rework public length and indexer properties for RefEnumerable --- .../Enumerables/ReadOnlyRefEnumerable{T}.cs | 169 ++++++++-------- .../Enumerables/RefEnumerable{T}.cs | 180 +++++++++--------- 2 files changed, 175 insertions(+), 174 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs index 831f95f7f1f..c251b0c1d57 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs @@ -29,11 +29,6 @@ public readonly ref struct ReadOnlyRefEnumerable /// /// The field maps to the total available length. private readonly ReadOnlySpan span; - - /// - /// Gets the total available length for the sequence. - /// - public int Length => span.Length; #else /// /// The target instance, if present. @@ -46,9 +41,9 @@ public readonly ref struct ReadOnlyRefEnumerable private readonly IntPtr offset; /// - /// Gets the total available length for the sequence. + /// The total available length for the sequence. /// - public int Length { get; } + private readonly int length; #endif /// @@ -57,40 +52,6 @@ public readonly ref struct ReadOnlyRefEnumerable /// The distance refers to items, not byte offset. private readonly int step; - /// - /// Gets the element at the specified zero-based index. - /// - /// A reference to the element at the specified index. - /// - /// Thrown when is invalid. - /// - public ref readonly T this[int index] - { - get - { - if ((uint)index >= (uint)Length) - { - ThrowHelper.ThrowIndexOutOfRangeException(); - } - - return ref DangerousGetReferenceAt(index); - } - } - -#if NETSTANDARD2_1_OR_GREATER - /// - /// Gets the element at the specified zero-based index. - /// - /// A reference to the element at the specified index. - /// - /// Thrown when is invalid. - /// - public ref readonly T this[Index index] - { - get => ref this[index.GetOffset(Length)]; - } -#endif - #if SPAN_RUNTIME_SUPPORT /// /// Initializes a new instance of the struct. @@ -155,53 +116,76 @@ internal ReadOnlyRefEnumerable(object? instance, IntPtr offset, int length, int { this.instance = instance; this.offset = offset; - Length = length; + this.length = length; this.step = step; } #endif /// - /// Returns a reference to the first element within the current instance, with no bounds check. + /// Gets the total available length for the sequence. /// - /// A reference to the first element within the current instance. - internal ref readonly T DangerousGetReference() + public int Length { #if SPAN_RUNTIME_SUPPORT - return ref MemoryMarshal.GetReference(this.span); + get => this.span.Length; #else - return ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); + get => this.length; #endif } /// - /// Returns a reference to a specified element within the current instance, with no bounds check. + /// Gets the element at the specified zero-based index. /// /// A reference to the element at the specified index. - internal ref readonly T DangerousGetReferenceAt(int index) + /// + /// Thrown when is invalid. + /// + public ref readonly T this[int index] { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((uint)index >= (uint)Length) + { + ThrowHelper.ThrowIndexOutOfRangeException(); + } + #if SPAN_RUNTIME_SUPPORT - ref T r0 = ref MemoryMarshal.GetReference(this.span); + ref T r0 = ref MemoryMarshal.GetReference(this.span); #else - ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); + ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); #endif + nint offset = (nint)(uint)index * (nint)(uint)this.step; + ref T ri = ref Unsafe.Add(ref r0, offset); + return ref ri; + } + } - // Here we just offset by shifting down as if we were traversing a 2D array with a - // a single column, with the width of each row represented by the step, the height - // represented by the current position, and with only the first element of each row - // being inspected. We can perform all the indexing operations in this type as nint, - // as the maximum offset is guaranteed never to exceed the maximum value, since on - // 32 bit architectures it's not possible to allocate that much memory anyway. - nint offset = (nint)(uint)index * (nint)(uint)this.step; - ref T ri = ref Unsafe.Add(ref r0, offset); - return ref ri; +#if NETSTANDARD2_1_OR_GREATER + /// + /// Gets the element at the specified zero-based index. + /// + /// A reference to the element at the specified index. + /// + /// Thrown when is invalid. + /// + public ref readonly T this[Index index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref this[index.GetOffset(Length)]; } +#endif /// [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public Enumerator GetEnumerator() { - return new Enumerator(this); +#if SPAN_RUNTIME_SUPPORT + return new Enumerator(this.span, this.step); +#else + return new Enumerator(this.instance, this.offset, this.length, this.step); +#endif } /// @@ -237,7 +221,7 @@ public void CopyTo(RefEnumerable destination) ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); ref T destinationRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(destination.Instance, destination.Offset); int - sourceLength = Length, + sourceLength = this.length, destinationLength = destination.Length; #endif @@ -262,7 +246,7 @@ public bool TryCopyTo(RefEnumerable destination) destinationLength = destination.Span.Length; #else int - sourceLength = Length, + sourceLength = this.length, destinationLength = destination.Length; #endif @@ -297,7 +281,7 @@ public void CopyTo(Span destination) int length = this.span.Length; #else ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); - int length = Length; + int length = this.length; #endif if ((uint)destination.Length < (uint)length) { @@ -319,7 +303,7 @@ public bool TryCopyTo(Span destination) #if SPAN_RUNTIME_SUPPORT int length = this.span.Length; #else - int length = Length; + int length = this.length; #endif if (destination.Length >= length) @@ -339,7 +323,7 @@ public T[] ToArray() #if SPAN_RUNTIME_SUPPORT int length = this.span.Length; #else - int length = Length; + int length = this.length; #endif // Empty array if no data is mapped @@ -374,10 +358,22 @@ public static implicit operator ReadOnlyRefEnumerable(RefEnumerable enumer /// public ref struct Enumerator { - /// - /// The used by this enumerator. - /// - private readonly ReadOnlyRefEnumerable enumerable; +#if SPAN_RUNTIME_SUPPORT + /// + private readonly ReadOnlySpan span; +#else + /// + private readonly object? instance; + + /// + private readonly IntPtr offset; + + /// + private readonly int length; +#endif + + /// + private readonly int step; /// /// The current position in the sequence. @@ -392,8 +388,10 @@ public ref struct Enumerator /// The distance between items in the sequence to enumerate. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Enumerator(ReadOnlySpan span, int step) - : this(new ReadOnlyRefEnumerable(span, step)) { + this.span = span; + this.step = step; + this.position = -1; } #else /// @@ -405,25 +403,23 @@ internal Enumerator(ReadOnlySpan span, int step) /// The distance between items in the sequence to enumerate. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Enumerator(object? instance, IntPtr offset, int length, int step) - : this(new ReadOnlyRefEnumerable(instance, offset, length, step)) { - } -#endif - - internal Enumerator(ReadOnlyRefEnumerable enumerable) - { - this.enumerable = enumerable; + this.instance = instance; + this.offset = offset; + this.length = length; + this.step = step; this.position = -1; } +#endif /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool MoveNext() { #if SPAN_RUNTIME_SUPPORT - return ++this.position < this.enumerable.span.Length; + return ++this.position < this.span.Length; #else - return ++this.position < this.enumerable.Length; + return ++this.position < this.length; #endif } @@ -431,7 +427,18 @@ public bool MoveNext() public readonly ref readonly T Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => ref this.enumerable.DangerousGetReferenceAt(this.position); + get + { +#if SPAN_RUNTIME_SUPPORT + ref T r0 = ref this.span.DangerousGetReference(); +#else + ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); +#endif + nint offset = (nint)(uint)this.position * (nint)(uint)this.step; + ref T ri = ref Unsafe.Add(ref r0, offset); + + return ref ri; + } } } diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs index 70fe1521239..a65454938c9 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs @@ -29,11 +29,6 @@ public readonly ref struct RefEnumerable /// /// The field maps to the total available length. internal readonly Span Span; - - /// - /// Gets the total available length for the sequence. - /// - public int Length => Span.Length; #else /// /// The target instance, if present. @@ -44,11 +39,6 @@ public readonly ref struct RefEnumerable /// The initial offset within . /// internal readonly IntPtr Offset; - - /// - /// Gets the total available length for the sequence. - /// - public int Length { get; } #endif /// @@ -57,40 +47,6 @@ public readonly ref struct RefEnumerable /// The distance refers to items, not byte offset. internal readonly int Step; - /// - /// Gets the element at the specified zero-based index. - /// - /// A reference to the element at the specified index. - /// - /// Thrown when is invalid. - /// - public ref T this[int index] - { - get - { - if ((uint)index >= (uint)Length) - { - ThrowHelper.ThrowIndexOutOfRangeException(); - } - - return ref DangerousGetReferenceAt(index); - } - } - -#if NETSTANDARD2_1_OR_GREATER - /// - /// Gets the element at the specified zero-based index. - /// - /// A reference to the element at the specified index. - /// - /// Thrown when is invalid. - /// - public ref T this[Index index] - { - get => ref this[index.GetOffset(Length)]; - } -#endif - #if SPAN_RUNTIME_SUPPORT /// /// Initializes a new instance of the struct. @@ -105,18 +61,6 @@ internal RefEnumerable(ref T reference, int length, int step) Step = step; } - /// - /// Initializes a new instance of the struct. - /// - /// The instance pointing to the first item in the target memory area. - /// The distance between items in the sequence to enumerate. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal RefEnumerable(Span span, int step) - { - Span = span; - Step = step; - } - /// /// Creates a new instance of the struct with the specified parameters. /// @@ -161,47 +105,70 @@ internal RefEnumerable(object? instance, IntPtr offset, int length, int step) #endif /// - /// Returns a reference to the first element within the current instance, with no bounds check. + /// Gets the total available length for the sequence. /// - /// A reference to the first element within the current instance. - internal ref T DangerousGetReference() + public int Length { #if SPAN_RUNTIME_SUPPORT - return ref MemoryMarshal.GetReference(this.Span); + get => this.Span.Length; #else - return ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset); + get; #endif } /// - /// Returns a reference to a specified element within the current instance, with no bounds check. + /// Gets the element at the specified zero-based index. /// /// A reference to the element at the specified index. - internal ref T DangerousGetReferenceAt(int index) + /// + /// Thrown when is invalid. + /// + public ref T this[int index] { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if ((uint)index >= (uint)Length) + { + ThrowHelper.ThrowIndexOutOfRangeException(); + } + #if SPAN_RUNTIME_SUPPORT - ref T r0 = ref MemoryMarshal.GetReference(this.Span); + ref T r0 = ref MemoryMarshal.GetReference(this.Span); #else - ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset); + ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.Instance, this.Offset); #endif + nint offset = (nint)(uint)index * (nint)(uint)this.Step; + ref T ri = ref Unsafe.Add(ref r0, offset); + return ref ri; + } + } - // Here we just offset by shifting down as if we were traversing a 2D array with a - // a single column, with the width of each row represented by the step, the height - // represented by the current position, and with only the first element of each row - // being inspected. We can perform all the indexing operations in this type as nint, - // as the maximum offset is guaranteed never to exceed the maximum value, since on - // 32 bit architectures it's not possible to allocate that much memory anyway. - nint offset = (nint)(uint)index * (nint)(uint)this.Step; - ref T ri = ref Unsafe.Add(ref r0, offset); - return ref ri; +#if NETSTANDARD2_1_OR_GREATER + /// + /// Gets the element at the specified zero-based index. + /// + /// A reference to the element at the specified index. + /// + /// Thrown when is invalid. + /// + public ref T this[Index index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref this[index.GetOffset(Length)]; } +#endif /// [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public Enumerator GetEnumerator() { - return new Enumerator(this); +#if SPAN_RUNTIME_SUPPORT + return new Enumerator(this.Span, this.Step); +#else + return new Enumerator(this.Instance, this.Offset, this.Length, this.Step); +#endif } /// @@ -472,10 +439,22 @@ public T[] ToArray() /// public ref struct Enumerator { - /// - /// The used by this enumerator. - /// - private readonly RefEnumerable enumerable; +#if SPAN_RUNTIME_SUPPORT + /// + private readonly Span span; +#else + /// + private readonly object? instance; + + /// + private readonly IntPtr offset; + + /// + private readonly int length; +#endif + + /// + private readonly int step; /// /// The current position in the sequence. @@ -490,8 +469,10 @@ public ref struct Enumerator /// The distance between items in the sequence to enumerate. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Enumerator(Span span, int step) - : this(new RefEnumerable(span, step)) { + this.span = span; + this.step = step; + this.position = -1; } #else /// @@ -503,28 +484,23 @@ internal Enumerator(Span span, int step) /// The distance between items in the sequence to enumerate. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Enumerator(object? instance, IntPtr offset, int length, int step) - : this(new RefEnumerable(instance, offset, length, step)) - { - } -#endif - - /// - /// Initializes a new instance of the struct. - /// - internal Enumerator(RefEnumerable enumerable) { - this.enumerable = enumerable; + this.instance = instance; + this.offset = offset; + this.length = length; + this.step = step; this.position = -1; } +#endif /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool MoveNext() { #if SPAN_RUNTIME_SUPPORT - return ++this.position < this.enumerable.Span.Length; + return ++this.position < this.span.Length; #else - return ++this.position < this.enumerable.Length; + return ++this.position < this.length; #endif } @@ -532,7 +508,25 @@ public bool MoveNext() public readonly ref T Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => ref this.enumerable.DangerousGetReferenceAt(this.position); + get + { +#if SPAN_RUNTIME_SUPPORT + ref T r0 = ref this.span.DangerousGetReference(); +#else + ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference(this.instance, this.offset); +#endif + + // Here we just offset by shifting down as if we were traversing a 2D array with a + // a single column, with the width of each row represented by the step, the height + // represented by the current position, and with only the first element of each row + // being inspected. We can perform all the indexing operations in this type as nint, + // as the maximum offset is guaranteed never to exceed the maximum value, since on + // 32 bit architectures it's not possible to allocate that much memory anyway. + nint offset = (nint)(uint)this.position * (nint)(uint)this.step; + ref T ri = ref Unsafe.Add(ref r0, offset); + + return ref ri; + } } } From dae4f84cd235910d0bd5211cacdbd9b49b8b1177 Mon Sep 17 00:00:00 2001 From: DaZombieKiller Date: Fri, 28 May 2021 21:55:06 +1000 Subject: [PATCH 4/8] Add AggressiveInlining to Length property on RefEnumerable --- .../Enumerables/ReadOnlyRefEnumerable{T}.cs | 3 ++- .../Enumerables/RefEnumerable{T}.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs index c251b0c1d57..ca82150a02f 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs @@ -10,7 +10,6 @@ #endif using Microsoft.Toolkit.HighPerformance.Helpers.Internals; using Microsoft.Toolkit.HighPerformance.Memory.Internals; - #if !SPAN_RUNTIME_SUPPORT using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers; #endif @@ -126,6 +125,7 @@ internal ReadOnlyRefEnumerable(object? instance, IntPtr offset, int length, int /// public int Length { + [MethodImpl(MethodImplOptions.AggressiveInlining)] #if SPAN_RUNTIME_SUPPORT get => this.span.Length; #else @@ -157,6 +157,7 @@ public ref readonly T this[int index] #endif nint offset = (nint)(uint)index * (nint)(uint)this.step; ref T ri = ref Unsafe.Add(ref r0, offset); + return ref ri; } } diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs index a65454938c9..7407781e2e5 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs @@ -10,7 +10,6 @@ #endif using Microsoft.Toolkit.HighPerformance.Helpers.Internals; using Microsoft.Toolkit.HighPerformance.Memory.Internals; - #if !SPAN_RUNTIME_SUPPORT using RuntimeHelpers = Microsoft.Toolkit.HighPerformance.Helpers.Internals.RuntimeHelpers; #endif @@ -109,6 +108,7 @@ internal RefEnumerable(object? instance, IntPtr offset, int length, int step) /// public int Length { + [MethodImpl(MethodImplOptions.AggressiveInlining)] #if SPAN_RUNTIME_SUPPORT get => this.Span.Length; #else @@ -140,6 +140,7 @@ public ref T this[int index] #endif nint offset = (nint)(uint)index * (nint)(uint)this.Step; ref T ri = ref Unsafe.Add(ref r0, offset); + return ref ri; } } From efd12430bf43436052bdbc11e5e34fe6a65e5499 Mon Sep 17 00:00:00 2001 From: DaZombieKiller Date: Fri, 28 May 2021 21:55:39 +1000 Subject: [PATCH 5/8] Add tests for RefEnumerable indexers --- .../Test_ReadOnlyRefEnumerable{T}.cs | 47 +++++++++++++++++++ .../Enumerables/Test_RefEnumerable{T}.cs | 47 +++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Enumerables/Test_ReadOnlyRefEnumerable{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Enumerables/Test_ReadOnlyRefEnumerable{T}.cs index d01dfb12239..bc8d50f57a8 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Enumerables/Test_ReadOnlyRefEnumerable{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Enumerables/Test_ReadOnlyRefEnumerable{T}.cs @@ -47,6 +47,53 @@ public void Test_ReadOnlyRefEnumerable_DangerousCreate_BelowZero(int length, int { _ = ReadOnlyRefEnumerable.DangerousCreate(in Unsafe.NullRef(), length, step); } + + [TestCategory("ReadOnlyRefEnumerable")] + [TestMethod] + [DataRow(1, 1, new[] { 1 })] + [DataRow(4, 1, new[] { 1, 2, 3, 4 })] + [DataRow(4, 4, new[] { 1, 5, 9, 13 })] + [DataRow(4, 5, new[] { 1, 6, 11, 16 })] + public void Test_ReadOnlyRefEnumerable_Indexer(int length, int step, int[] values) + { + Span data = new[] + { + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16 + }; + + ReadOnlyRefEnumerable enumerable = ReadOnlyRefEnumerable.DangerousCreate(in data[0], length, step); + + for (int i = 0; i < enumerable.Length; i++) + { + Assert.AreEqual(enumerable[i], values[i]); + } + } + +#if NETCOREAPP3_1_OR_GREATER + [TestCategory("ReadOnlyRefEnumerable")] + [TestMethod] + [DataRow(1, 1, new[] { 1 })] + [DataRow(4, 1, new[] { 1, 2, 3, 4 })] + [DataRow(4, 4, new[] { 1, 5, 9, 13 })] + [DataRow(4, 5, new[] { 1, 6, 11, 16 })] + public void Test_ReadOnlyRefEnumerable_Index_Indexer(int length, int step, int[] values) + { + Span data = new[] + { + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16 + }; + + ReadOnlyRefEnumerable enumerable = ReadOnlyRefEnumerable.DangerousCreate(in data[0], length, step); + + Assert.AreEqual(values[^1], enumerable[^1]); + } +#endif } } diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Enumerables/Test_RefEnumerable{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Enumerables/Test_RefEnumerable{T}.cs index 0442be83ab3..8d46d7598f3 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Enumerables/Test_RefEnumerable{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Enumerables/Test_RefEnumerable{T}.cs @@ -47,6 +47,53 @@ public void Test_RefEnumerable_DangerousCreate_BelowZero(int length, int step) { _ = RefEnumerable.DangerousCreate(ref Unsafe.NullRef(), length, step); } + + [TestCategory("RefEnumerable")] + [TestMethod] + [DataRow(1, 1, new[] { 1 })] + [DataRow(4, 1, new[] { 1, 2, 3, 4 })] + [DataRow(4, 4, new[] { 1, 5, 9, 13 })] + [DataRow(4, 5, new[] { 1, 6, 11, 16 })] + public void Test_RefEnumerable_Indexer(int length, int step, int[] values) + { + Span data = new[] + { + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16 + }; + + RefEnumerable enumerable = RefEnumerable.DangerousCreate(ref data[0], length, step); + + for (int i = 0; i < enumerable.Length; i++) + { + Assert.AreEqual(enumerable[i], values[i]); + } + } + +#if NETCOREAPP3_1_OR_GREATER + [TestCategory("RefEnumerable")] + [TestMethod] + [DataRow(1, 1, new[] { 1 })] + [DataRow(4, 1, new[] { 1, 2, 3, 4 })] + [DataRow(4, 4, new[] { 1, 5, 9, 13 })] + [DataRow(4, 5, new[] { 1, 6, 11, 16 })] + public void Test_RefEnumerable_Index_Indexer(int length, int step, int[] values) + { + Span data = new[] + { + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16 + }; + + RefEnumerable enumerable = RefEnumerable.DangerousCreate(ref data[0], length, step); + + Assert.AreEqual(values[^1], enumerable[^1]); + } +#endif } } From e4810fb012a468269d862359fde7eea55025ea6c Mon Sep 17 00:00:00 2001 From: DaZombieKiller Date: Sat, 29 May 2021 01:28:49 +1000 Subject: [PATCH 6/8] Add XML documentation for index parameter on RefEnumerable indexers --- .../Enumerables/ReadOnlyRefEnumerable{T}.cs | 2 ++ .../Enumerables/RefEnumerable{T}.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs index ca82150a02f..aa4507fafe8 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs @@ -136,6 +136,7 @@ public int Length /// /// Gets the element at the specified zero-based index. /// + /// The zero-based index of the element. /// A reference to the element at the specified index. /// /// Thrown when is invalid. @@ -166,6 +167,7 @@ public ref readonly T this[int index] /// /// Gets the element at the specified zero-based index. /// + /// The zero-based index of the element. /// A reference to the element at the specified index. /// /// Thrown when is invalid. diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs index 7407781e2e5..535b742befa 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs @@ -119,6 +119,7 @@ public int Length /// /// Gets the element at the specified zero-based index. /// + /// The zero-based index of the element. /// A reference to the element at the specified index. /// /// Thrown when is invalid. @@ -149,6 +150,7 @@ public ref T this[int index] /// /// Gets the element at the specified zero-based index. /// + /// The zero-based index of the element. /// A reference to the element at the specified index. /// /// Thrown when is invalid. From 5a6bf626c093c52b23f90980f801eb610e9b545c Mon Sep 17 00:00:00 2001 From: DaZombieKiller Date: Fri, 11 Jun 2021 20:20:16 +1000 Subject: [PATCH 7/8] Add RefEnumerable indexer exception tests --- .../Test_ReadOnlyRefEnumerable{T}.cs | 55 ++++++++++++++----- .../Enumerables/Test_RefEnumerable{T}.cs | 53 ++++++++++++++---- 2 files changed, 83 insertions(+), 25 deletions(-) diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Enumerables/Test_ReadOnlyRefEnumerable{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Enumerables/Test_ReadOnlyRefEnumerable{T}.cs index bc8d50f57a8..f5631f8e3d4 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Enumerables/Test_ReadOnlyRefEnumerable{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Enumerables/Test_ReadOnlyRefEnumerable{T}.cs @@ -50,11 +50,11 @@ public void Test_ReadOnlyRefEnumerable_DangerousCreate_BelowZero(int length, int [TestCategory("ReadOnlyRefEnumerable")] [TestMethod] - [DataRow(1, 1, new[] { 1 })] - [DataRow(4, 1, new[] { 1, 2, 3, 4 })] - [DataRow(4, 4, new[] { 1, 5, 9, 13 })] - [DataRow(4, 5, new[] { 1, 6, 11, 16 })] - public void Test_ReadOnlyRefEnumerable_Indexer(int length, int step, int[] values) + [DataRow(1, new[] { 1 })] + [DataRow(1, new[] { 1, 2, 3, 4 })] + [DataRow(4, new[] { 1, 5, 9, 13 })] + [DataRow(5, new[] { 1, 6, 11, 16 })] + public void Test_ReadOnlyRefEnumerable_Indexer(int step, int[] values) { Span data = new[] { @@ -64,7 +64,7 @@ public void Test_ReadOnlyRefEnumerable_Indexer(int length, int step, int[] value 13, 14, 15, 16 }; - ReadOnlyRefEnumerable enumerable = ReadOnlyRefEnumerable.DangerousCreate(in data[0], length, step); + ReadOnlyRefEnumerable enumerable = ReadOnlyRefEnumerable.DangerousCreate(in data[0], values.Length, step); for (int i = 0; i < enumerable.Length; i++) { @@ -72,14 +72,27 @@ public void Test_ReadOnlyRefEnumerable_Indexer(int length, int step, int[] value } } + [TestCategory("ReadOnlyRefEnumerable")] + [TestMethod] + public void Test_ReadOnlyRefEnumerable_Indexer_ThrowsIndexOutOfRange() + { + int[] array = new[] + { + 0, 0, 0, 0 + }; + + Assert.ThrowsException(() => ReadOnlyRefEnumerable.DangerousCreate(in array[0], array.Length, 1)[-1]); + Assert.ThrowsException(() => ReadOnlyRefEnumerable.DangerousCreate(in array[0], array.Length, 1)[array.Length]); + } + #if NETCOREAPP3_1_OR_GREATER [TestCategory("ReadOnlyRefEnumerable")] [TestMethod] - [DataRow(1, 1, new[] { 1 })] - [DataRow(4, 1, new[] { 1, 2, 3, 4 })] - [DataRow(4, 4, new[] { 1, 5, 9, 13 })] - [DataRow(4, 5, new[] { 1, 6, 11, 16 })] - public void Test_ReadOnlyRefEnumerable_Index_Indexer(int length, int step, int[] values) + [DataRow(1, new[] { 1 })] + [DataRow(1, new[] { 1, 2, 3, 4 })] + [DataRow(4, new[] { 1, 5, 9, 13 })] + [DataRow(5, new[] { 1, 6, 11, 16 })] + public void Test_ReadOnlyRefEnumerable_Index_Indexer(int step, int[] values) { Span data = new[] { @@ -89,9 +102,25 @@ public void Test_ReadOnlyRefEnumerable_Index_Indexer(int length, int step, int[] 13, 14, 15, 16 }; - ReadOnlyRefEnumerable enumerable = ReadOnlyRefEnumerable.DangerousCreate(in data[0], length, step); + ReadOnlyRefEnumerable enumerable = ReadOnlyRefEnumerable.DangerousCreate(in data[0], values.Length, step); + + for (int i = 1; i <= enumerable.Length; i++) + { + Assert.AreEqual(values[^i], enumerable[^i]); + } + } + + [TestCategory("ReadOnlyRefEnumerable")] + [TestMethod] + public void Test_ReadOnlyRefEnumerable_Index_Indexer_ThrowsIndexOutOfRange() + { + int[] array = new[] + { + 0, 0, 0, 0 + }; - Assert.AreEqual(values[^1], enumerable[^1]); + Assert.ThrowsException(() => ReadOnlyRefEnumerable.DangerousCreate(in array[0], array.Length, 1)[new Index(array.Length)]); + Assert.ThrowsException(() => ReadOnlyRefEnumerable.DangerousCreate(in array[0], array.Length, 1)[^0]); } #endif } diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Enumerables/Test_RefEnumerable{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Enumerables/Test_RefEnumerable{T}.cs index 8d46d7598f3..a0c9a152c43 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Enumerables/Test_RefEnumerable{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Enumerables/Test_RefEnumerable{T}.cs @@ -50,11 +50,11 @@ public void Test_RefEnumerable_DangerousCreate_BelowZero(int length, int step) [TestCategory("RefEnumerable")] [TestMethod] - [DataRow(1, 1, new[] { 1 })] - [DataRow(4, 1, new[] { 1, 2, 3, 4 })] - [DataRow(4, 4, new[] { 1, 5, 9, 13 })] - [DataRow(4, 5, new[] { 1, 6, 11, 16 })] - public void Test_RefEnumerable_Indexer(int length, int step, int[] values) + [DataRow(1, new[] { 1 })] + [DataRow(1, new[] { 1, 2, 3, 4 })] + [DataRow(4, new[] { 1, 5, 9, 13 })] + [DataRow(5, new[] { 1, 6, 11, 16 })] + public void Test_RefEnumerable_Indexer(int step, int[] values) { Span data = new[] { @@ -64,7 +64,7 @@ public void Test_RefEnumerable_Indexer(int length, int step, int[] values) 13, 14, 15, 16 }; - RefEnumerable enumerable = RefEnumerable.DangerousCreate(ref data[0], length, step); + RefEnumerable enumerable = RefEnumerable.DangerousCreate(ref data[0], values.Length, step); for (int i = 0; i < enumerable.Length; i++) { @@ -72,13 +72,26 @@ public void Test_RefEnumerable_Indexer(int length, int step, int[] values) } } + [TestCategory("RefEnumerable")] + [TestMethod] + public void Test_RefEnumerable_Indexer_ThrowsIndexOutOfRange() + { + int[] array = new[] + { + 0, 0, 0, 0 + }; + + Assert.ThrowsException(() => RefEnumerable.DangerousCreate(ref array[0], array.Length, 1)[-1]); + Assert.ThrowsException(() => RefEnumerable.DangerousCreate(ref array[0], array.Length, 1)[array.Length]); + } + #if NETCOREAPP3_1_OR_GREATER [TestCategory("RefEnumerable")] [TestMethod] - [DataRow(1, 1, new[] { 1 })] - [DataRow(4, 1, new[] { 1, 2, 3, 4 })] - [DataRow(4, 4, new[] { 1, 5, 9, 13 })] - [DataRow(4, 5, new[] { 1, 6, 11, 16 })] + [DataRow(1, new[] { 1 })] + [DataRow(1, new[] { 1, 2, 3, 4 })] + [DataRow(4, new[] { 1, 5, 9, 13 })] + [DataRow(5, new[] { 1, 6, 11, 16 })] public void Test_RefEnumerable_Index_Indexer(int length, int step, int[] values) { Span data = new[] @@ -89,9 +102,25 @@ public void Test_RefEnumerable_Index_Indexer(int length, int step, int[] values) 13, 14, 15, 16 }; - RefEnumerable enumerable = RefEnumerable.DangerousCreate(ref data[0], length, step); + RefEnumerable enumerable = RefEnumerable.DangerousCreate(ref data[0], values.Length, step); + + for (int i = 1; i <= enumerable.Length; i++) + { + Assert.AreEqual(values[^i], enumerable[^i]); + } + } + + [TestCategory("RefEnumerable")] + [TestMethod] + public void Test_RefEnumerable_Index_Indexer_ThrowsIndexOutOfRange() + { + int[] array = new[] + { + 0, 0, 0, 0 + }; - Assert.AreEqual(values[^1], enumerable[^1]); + Assert.ThrowsException(() => RefEnumerable.DangerousCreate(ref array[0], array.Length, 1)[new Index(array.Length)]); + Assert.ThrowsException(() => RefEnumerable.DangerousCreate(ref array[0], array.Length, 1)[^0]); } #endif } From 16b372fd975c80743bf7a6e117ec9f6d8f36e5f4 Mon Sep 17 00:00:00 2001 From: DaZombieKiller Date: Fri, 11 Jun 2021 20:25:55 +1000 Subject: [PATCH 8/8] Remove missed length parameter on RefEnumerable test --- .../Enumerables/Test_RefEnumerable{T}.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Enumerables/Test_RefEnumerable{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Enumerables/Test_RefEnumerable{T}.cs index a0c9a152c43..d60e4fe2c48 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Enumerables/Test_RefEnumerable{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Enumerables/Test_RefEnumerable{T}.cs @@ -92,7 +92,7 @@ public void Test_RefEnumerable_Indexer_ThrowsIndexOutOfRange() [DataRow(1, new[] { 1, 2, 3, 4 })] [DataRow(4, new[] { 1, 5, 9, 13 })] [DataRow(5, new[] { 1, 6, 11, 16 })] - public void Test_RefEnumerable_Index_Indexer(int length, int step, int[] values) + public void Test_RefEnumerable_Index_Indexer(int step, int[] values) { Span data = new[] {