Skip to content

Commit 55fbf93

Browse files
author
DaZombieKiller
committed
Expose public indexer and length properties on RefEnumerable<T> and ReadOnlyRefEnumerable<T>
1 parent 63cbb4a commit 55fbf93

File tree

2 files changed

+207
-105
lines changed

2 files changed

+207
-105
lines changed

Microsoft.Toolkit.HighPerformance/Enumerables/ReadOnlyRefEnumerable{T}.cs

Lines changed: 98 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ public readonly ref struct ReadOnlyRefEnumerable<T>
2929
/// </summary>
3030
/// <remarks>The <see cref="ReadOnlySpan{T}.Length"/> field maps to the total available length.</remarks>
3131
private readonly ReadOnlySpan<T> span;
32+
33+
/// <summary>
34+
/// Gets the total available length for the sequence.
35+
/// </summary>
36+
public int Length => span.Length;
3237
#else
3338
/// <summary>
3439
/// The target <see cref="object"/> instance, if present.
@@ -41,9 +46,9 @@ public readonly ref struct ReadOnlyRefEnumerable<T>
4146
private readonly IntPtr offset;
4247

4348
/// <summary>
44-
/// The total available length for the sequence.
49+
/// Gets the total available length for the sequence.
4550
/// </summary>
46-
private readonly int length;
51+
public int Length { get; }
4752
#endif
4853

4954
/// <summary>
@@ -52,6 +57,40 @@ public readonly ref struct ReadOnlyRefEnumerable<T>
5257
/// <remarks>The distance refers to <typeparamref name="T"/> items, not byte offset.</remarks>
5358
private readonly int step;
5459

60+
/// <summary>
61+
/// Gets the element at the specified zero-based index.
62+
/// </summary>
63+
/// <returns>A reference to the element at the specified index.</returns>
64+
/// <exception cref="IndexOutOfRangeException">
65+
/// Thrown when <paramref name="index"/> is invalid.
66+
/// </exception>
67+
public ref readonly T this[int index]
68+
{
69+
get
70+
{
71+
if ((uint)index >= (uint)Length)
72+
{
73+
ThrowHelper.ThrowIndexOutOfRangeException();
74+
}
75+
76+
return ref DangerousGetReferenceAt(index);
77+
}
78+
}
79+
80+
#if NETSTANDARD2_1_OR_GREATER
81+
/// <summary>
82+
/// Gets the element at the specified zero-based index.
83+
/// </summary>
84+
/// <returns>A reference to the element at the specified index.</returns>
85+
/// <exception cref="IndexOutOfRangeException">
86+
/// Thrown when <paramref name="index"/> is invalid.
87+
/// </exception>
88+
public ref readonly T this[Index index]
89+
{
90+
get => ref this[index.GetOffset(Length)];
91+
}
92+
#endif
93+
5594
#if SPAN_RUNTIME_SUPPORT
5695
/// <summary>
5796
/// Initializes a new instance of the <see cref="ReadOnlyRefEnumerable{T}"/> struct.
@@ -116,21 +155,52 @@ internal ReadOnlyRefEnumerable(object? instance, IntPtr offset, int length, int
116155
{
117156
this.instance = instance;
118157
this.offset = offset;
119-
this.length = length;
158+
Length = length;
120159
this.step = step;
121160
}
122161
#endif
123162

163+
/// <summary>
164+
/// Returns a reference to the first element within the current instance, with no bounds check.
165+
/// </summary>
166+
/// <returns>A reference to the first element within the current instance.</returns>
167+
internal ref readonly T DangerousGetReference()
168+
{
169+
#if SPAN_RUNTIME_SUPPORT
170+
return ref MemoryMarshal.GetReference(this.span);
171+
#else
172+
return ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.instance, this.offset);
173+
#endif
174+
}
175+
176+
/// <summary>
177+
/// Returns a reference to a specified element within the current instance, with no bounds check.
178+
/// </summary>
179+
/// <returns>A reference to the element at the specified index.</returns>
180+
internal ref readonly T DangerousGetReferenceAt(int index)
181+
{
182+
#if SPAN_RUNTIME_SUPPORT
183+
ref T r0 = ref MemoryMarshal.GetReference(this.span);
184+
#else
185+
ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.instance, this.offset);
186+
#endif
187+
// Here we just offset by shifting down as if we were traversing a 2D array with a
188+
// a single column, with the width of each row represented by the step, the height
189+
// represented by the current position, and with only the first element of each row
190+
// being inspected. We can perform all the indexing operations in this type as nint,
191+
// as the maximum offset is guaranteed never to exceed the maximum value, since on
192+
// 32 bit architectures it's not possible to allocate that much memory anyway.
193+
nint offset = (nint)(uint)index * (nint)(uint)this.step;
194+
ref T ri = ref Unsafe.Add(ref r0, offset);
195+
return ref ri;
196+
}
197+
124198
/// <inheritdoc cref="System.Collections.IEnumerable.GetEnumerator"/>
125199
[Pure]
126200
[MethodImpl(MethodImplOptions.AggressiveInlining)]
127201
public Enumerator GetEnumerator()
128202
{
129-
#if SPAN_RUNTIME_SUPPORT
130-
return new Enumerator(this.span, this.step);
131-
#else
132-
return new Enumerator(this.instance, this.offset, this.length, this.step);
133-
#endif
203+
return new Enumerator(this);
134204
}
135205

136206
/// <summary>
@@ -166,7 +236,7 @@ public void CopyTo(RefEnumerable<T> destination)
166236
ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.instance, this.offset);
167237
ref T destinationRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(destination.Instance, destination.Offset);
168238
int
169-
sourceLength = this.length,
239+
sourceLength = Length,
170240
destinationLength = destination.Length;
171241
#endif
172242

@@ -191,7 +261,7 @@ public bool TryCopyTo(RefEnumerable<T> destination)
191261
destinationLength = destination.Span.Length;
192262
#else
193263
int
194-
sourceLength = this.length,
264+
sourceLength = Length,
195265
destinationLength = destination.Length;
196266
#endif
197267

@@ -226,7 +296,7 @@ public void CopyTo(Span<T> destination)
226296
int length = this.span.Length;
227297
#else
228298
ref T sourceRef = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.instance, this.offset);
229-
int length = this.length;
299+
int length = Length;
230300
#endif
231301
if ((uint)destination.Length < (uint)length)
232302
{
@@ -248,7 +318,7 @@ public bool TryCopyTo(Span<T> destination)
248318
#if SPAN_RUNTIME_SUPPORT
249319
int length = this.span.Length;
250320
#else
251-
int length = this.length;
321+
int length = Length;
252322
#endif
253323

254324
if (destination.Length >= length)
@@ -268,7 +338,7 @@ public T[] ToArray()
268338
#if SPAN_RUNTIME_SUPPORT
269339
int length = this.span.Length;
270340
#else
271-
int length = this.length;
341+
int length = Length;
272342
#endif
273343

274344
// Empty array if no data is mapped
@@ -303,22 +373,10 @@ public static implicit operator ReadOnlyRefEnumerable<T>(RefEnumerable<T> enumer
303373
/// </summary>
304374
public ref struct Enumerator
305375
{
306-
#if SPAN_RUNTIME_SUPPORT
307-
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}.span"/>
308-
private readonly ReadOnlySpan<T> span;
309-
#else
310-
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}.instance"/>
311-
private readonly object? instance;
312-
313-
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}.offset"/>
314-
private readonly IntPtr offset;
315-
316-
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}.length"/>
317-
private readonly int length;
318-
#endif
319-
320-
/// <inheritdoc cref="ReadOnlyRefEnumerable{T}.step"/>
321-
private readonly int step;
376+
/// <summary>
377+
/// The <see cref="ReadOnlyRefEnumerable{T}"/> used by this enumerator.
378+
/// </summary>
379+
private readonly ReadOnlyRefEnumerable<T> enumerable;
322380

323381
/// <summary>
324382
/// The current position in the sequence.
@@ -333,10 +391,8 @@ public ref struct Enumerator
333391
/// <param name="step">The distance between items in the sequence to enumerate.</param>
334392
[MethodImpl(MethodImplOptions.AggressiveInlining)]
335393
internal Enumerator(ReadOnlySpan<T> span, int step)
394+
: this(new ReadOnlyRefEnumerable<T>(span, step))
336395
{
337-
this.span = span;
338-
this.step = step;
339-
this.position = -1;
340396
}
341397
#else
342398
/// <summary>
@@ -348,42 +404,33 @@ internal Enumerator(ReadOnlySpan<T> span, int step)
348404
/// <param name="step">The distance between items in the sequence to enumerate.</param>
349405
[MethodImpl(MethodImplOptions.AggressiveInlining)]
350406
internal Enumerator(object? instance, IntPtr offset, int length, int step)
407+
: this(new ReadOnlyRefEnumerable<T>(instance, offset, length, step))
351408
{
352-
this.instance = instance;
353-
this.offset = offset;
354-
this.length = length;
355-
this.step = step;
356-
this.position = -1;
357409
}
358410
#endif
359411

412+
internal Enumerator(ReadOnlyRefEnumerable<T> enumerable)
413+
{
414+
this.enumerable = enumerable;
415+
this.position = -1;
416+
}
417+
360418
/// <inheritdoc cref="System.Collections.IEnumerator.MoveNext"/>
361419
[MethodImpl(MethodImplOptions.AggressiveInlining)]
362420
public bool MoveNext()
363421
{
364422
#if SPAN_RUNTIME_SUPPORT
365-
return ++this.position < this.span.Length;
423+
return ++this.position < this.enumerable.span.Length;
366424
#else
367-
return ++this.position < this.length;
425+
return ++this.position < this.enumerable.Length;
368426
#endif
369427
}
370428

371429
/// <inheritdoc cref="System.Collections.Generic.IEnumerator{T}.Current"/>
372430
public readonly ref readonly T Current
373431
{
374432
[MethodImpl(MethodImplOptions.AggressiveInlining)]
375-
get
376-
{
377-
#if SPAN_RUNTIME_SUPPORT
378-
ref T r0 = ref this.span.DangerousGetReference();
379-
#else
380-
ref T r0 = ref RuntimeHelpers.GetObjectDataAtOffsetOrPointerReference<T>(this.instance, this.offset);
381-
#endif
382-
nint offset = (nint)(uint)this.position * (nint)(uint)this.step;
383-
ref T ri = ref Unsafe.Add(ref r0, offset);
384-
385-
return ref ri;
386-
}
433+
get => ref this.enumerable.DangerousGetReferenceAt(this.position);
387434
}
388435
}
389436

0 commit comments

Comments
 (0)