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
}
}