Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -121,6 +120,65 @@ internal ReadOnlyRefEnumerable(object? instance, IntPtr offset, int length, int
}
#endif

/// <summary>
/// Gets the total available length for the sequence.
/// </summary>
public int Length
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#if SPAN_RUNTIME_SUPPORT
get => this.span.Length;
#else
get => this.length;
#endif
}

/// <summary>
/// Gets the element at the specified zero-based index.
/// </summary>
/// <param name="index">The zero-based index of the element.</param>
/// <returns>A reference to the element at the specified index.</returns>
/// <exception cref="IndexOutOfRangeException">
/// Thrown when <paramref name="index"/> is invalid.
/// </exception>
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<T>(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
/// <summary>
/// Gets the element at the specified zero-based index.
/// </summary>
/// <param name="index">The zero-based index of the element.</param>
/// <returns>A reference to the element at the specified index.</returns>
/// <exception cref="IndexOutOfRangeException">
/// Thrown when <paramref name="index"/> is invalid.
/// </exception>
public ref readonly T this[Index index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref this[index.GetOffset(Length)];
}
#endif

/// <inheritdoc cref="System.Collections.IEnumerable.GetEnumerator"/>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down
67 changes: 60 additions & 7 deletions Microsoft.Toolkit.HighPerformance/Enumerables/RefEnumerable{T}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -39,11 +38,6 @@ public readonly ref struct RefEnumerable<T>
/// The initial offset within <see cref="Instance"/>.
/// </summary>
internal readonly IntPtr Offset;

/// <summary>
/// The total available length for the sequence.
/// </summary>
internal readonly int Length;
#endif

/// <summary>
Expand Down Expand Up @@ -109,6 +103,65 @@ internal RefEnumerable(object? instance, IntPtr offset, int length, int step)
}
#endif

/// <summary>
/// Gets the total available length for the sequence.
/// </summary>
public int Length
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#if SPAN_RUNTIME_SUPPORT
get => this.Span.Length;
#else
get;
#endif
}

/// <summary>
/// Gets the element at the specified zero-based index.
/// </summary>
/// <param name="index">The zero-based index of the element.</param>
/// <returns>A reference to the element at the specified index.</returns>
/// <exception cref="IndexOutOfRangeException">
/// Thrown when <paramref name="index"/> is invalid.
/// </exception>
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<T>(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
/// <summary>
/// Gets the element at the specified zero-based index.
/// </summary>
/// <param name="index">The zero-based index of the element.</param>
/// <returns>A reference to the element at the specified index.</returns>
/// <exception cref="IndexOutOfRangeException">
/// Thrown when <paramref name="index"/> is invalid.
/// </exception>
public ref T this[Index index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref this[index.GetOffset(Length)];
}
#endif

/// <inheritdoc cref="System.Collections.IEnumerable.GetEnumerator"/>
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,82 @@ public void Test_ReadOnlyRefEnumerable_DangerousCreate_BelowZero(int length, int
{
_ = ReadOnlyRefEnumerable<int>.DangerousCreate(in Unsafe.NullRef<int>(), 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<int> data = new[]
{
1, 2, 3, 4,
5, 6, 7, 8,
9, 10, 11, 12,
13, 14, 15, 16
};

ReadOnlyRefEnumerable<int> enumerable = ReadOnlyRefEnumerable<int>.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<IndexOutOfRangeException>(() => ReadOnlyRefEnumerable<int>.DangerousCreate(in array[0], array.Length, 1)[-1]);
Assert.ThrowsException<IndexOutOfRangeException>(() => ReadOnlyRefEnumerable<int>.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<int> data = new[]
{
1, 2, 3, 4,
5, 6, 7, 8,
9, 10, 11, 12,
13, 14, 15, 16
};

ReadOnlyRefEnumerable<int> enumerable = ReadOnlyRefEnumerable<int>.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<IndexOutOfRangeException>(() => ReadOnlyRefEnumerable<int>.DangerousCreate(in array[0], array.Length, 1)[new Index(array.Length)]);
Assert.ThrowsException<IndexOutOfRangeException>(() => ReadOnlyRefEnumerable<int>.DangerousCreate(in array[0], array.Length, 1)[^0]);
}
#endif
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,82 @@ public void Test_RefEnumerable_DangerousCreate_BelowZero(int length, int step)
{
_ = RefEnumerable<int>.DangerousCreate(ref Unsafe.NullRef<int>(), 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<int> data = new[]
{
1, 2, 3, 4,
5, 6, 7, 8,
9, 10, 11, 12,
13, 14, 15, 16
};

RefEnumerable<int> enumerable = RefEnumerable<int>.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<IndexOutOfRangeException>(() => RefEnumerable<int>.DangerousCreate(ref array[0], array.Length, 1)[-1]);
Assert.ThrowsException<IndexOutOfRangeException>(() => RefEnumerable<int>.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<int> data = new[]
{
1, 2, 3, 4,
5, 6, 7, 8,
9, 10, 11, 12,
13, 14, 15, 16
};

RefEnumerable<int> enumerable = RefEnumerable<int>.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<IndexOutOfRangeException>(() => RefEnumerable<int>.DangerousCreate(ref array[0], array.Length, 1)[new Index(array.Length)]);
Assert.ThrowsException<IndexOutOfRangeException>(() => RefEnumerable<int>.DangerousCreate(ref array[0], array.Length, 1)[^0]);
}
#endif
}
}

Expand Down