diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs index ad5fc7e3519..aa4507fafe8 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 @@ -121,6 +120,65 @@ internal ReadOnlyRefEnumerable(object? instance, IntPtr offset, int length, int } #endif + /// + /// Gets the total available length for the sequence. + /// + public int Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#if SPAN_RUNTIME_SUPPORT + get => this.span.Length; +#else + get => this.length; +#endif + } + + /// + /// 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. + /// + 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); +#else + 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; + } + } + +#if NETSTANDARD2_1_OR_GREATER + /// + /// 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. + /// + public ref readonly T this[Index index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref this[index.GetOffset(Length)]; + } +#endif + /// [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs index 3ceee9311a9..535b742befa 100644 --- a/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs +++ b/Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs @@ -9,9 +9,8 @@ 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 @@ -39,11 +38,6 @@ public readonly ref struct RefEnumerable /// The initial offset within . /// internal readonly IntPtr Offset; - - /// - /// The total available length for the sequence. - /// - internal readonly int Length; #endif /// @@ -109,6 +103,65 @@ internal RefEnumerable(object? instance, IntPtr offset, int length, int step) } #endif + /// + /// Gets the total available length for the sequence. + /// + public int Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#if SPAN_RUNTIME_SUPPORT + get => this.Span.Length; +#else + get; +#endif + } + + /// + /// 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. + /// + 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); +#else + 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; + } + } + +#if NETSTANDARD2_1_OR_GREATER + /// + /// 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. + /// + public ref T this[Index index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref this[index.GetOffset(Length)]; + } +#endif + /// [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/UnitTests/UnitTests.HighPerformance.Shared/Enumerables/Test_ReadOnlyRefEnumerable{T}.cs b/UnitTests/UnitTests.HighPerformance.Shared/Enumerables/Test_ReadOnlyRefEnumerable{T}.cs index d01dfb12239..f5631f8e3d4 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Enumerables/Test_ReadOnlyRefEnumerable{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Enumerables/Test_ReadOnlyRefEnumerable{T}.cs @@ -47,6 +47,82 @@ public void Test_ReadOnlyRefEnumerable_DangerousCreate_BelowZero(int length, int { _ = ReadOnlyRefEnumerable.DangerousCreate(in Unsafe.NullRef(), length, step); } + + [TestCategory("ReadOnlyRefEnumerable")] + [TestMethod] + [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[] + { + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16 + }; + + ReadOnlyRefEnumerable enumerable = ReadOnlyRefEnumerable.DangerousCreate(in data[0], values.Length, step); + + for (int i = 0; i < enumerable.Length; i++) + { + Assert.AreEqual(enumerable[i], values[i]); + } + } + + [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, 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[] + { + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16 + }; + + 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.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 0442be83ab3..d60e4fe2c48 100644 --- a/UnitTests/UnitTests.HighPerformance.Shared/Enumerables/Test_RefEnumerable{T}.cs +++ b/UnitTests/UnitTests.HighPerformance.Shared/Enumerables/Test_RefEnumerable{T}.cs @@ -47,6 +47,82 @@ public void Test_RefEnumerable_DangerousCreate_BelowZero(int length, int step) { _ = RefEnumerable.DangerousCreate(ref Unsafe.NullRef(), length, step); } + + [TestCategory("RefEnumerable")] + [TestMethod] + [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[] + { + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16 + }; + + RefEnumerable enumerable = RefEnumerable.DangerousCreate(ref data[0], values.Length, step); + + for (int i = 0; i < enumerable.Length; i++) + { + Assert.AreEqual(enumerable[i], values[i]); + } + } + + [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, 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 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], 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.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 } }