Skip to content

Commit a1b6ac8

Browse files
authored
Merge pull request #13 from rameel/cleanup
Cleanup and formatting
2 parents 1690c46 + 6b476cb commit a1b6ac8

13 files changed

Lines changed: 322 additions & 166 deletions

Ramstack.Structures.Tests/Collections/ReadOnlyArrayTests.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,19 @@ public void Operator_Conversions(string? value)
583583
}
584584
}
585585

586+
[TestCase(0)]
587+
[TestCase(1)]
588+
[TestCase(9)]
589+
public void Linq_ToList(int count)
590+
{
591+
var array = CreateArray(count).ToReadOnlyArray();
592+
var list = array.ToList();
593+
594+
Assert.That(list.Count, Is.EqualTo(count));
595+
Assert.That(list.Capacity, Is.EqualTo(count));
596+
Assert.That(list, Is.EqualTo(array));
597+
}
598+
586599
private static int[] CreateArray(int length) =>
587600
Enumerable.Range(0, length).ToArray();
588601
}

Ramstack.Structures/Collections/ArrayViewExtensions.cs

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ public static ArrayView<T> AsView<T>(this List<T>? list)
148148
{
149149
if (list is not null)
150150
{
151-
var array = ListAccessor<T>.GetBuffer(list);
151+
var array = ListAccessor<T>.GetArray(list);
152152
var count = Math.Min(list.Count, array.Length);
153153

154154
return new ArrayView<T>(array, 0, count);
@@ -157,26 +157,5 @@ public static ArrayView<T> AsView<T>(this List<T>? list)
157157
return ArrayView<T>.Empty;
158158
}
159159

160-
#region Inner type: ListAccessor<T>
161-
162-
/// <summary>
163-
/// Provides low-level access to the internal array buffer of a <see cref="List{T}"/>.
164-
/// </summary>
165-
/// <typeparam name="T">The type of the elements in the list.</typeparam>
166-
private static class ListAccessor<T>
167-
{
168-
/// <summary>
169-
/// Retrieves a reference to the internal array buffer of the specified <see cref="List{T}"/>.
170-
/// </summary>
171-
/// <param name="list">The list whose internal buffer is to be accessed.</param>
172-
/// <returns>
173-
/// A reference to the internal array used by the list.
174-
/// </returns>
175-
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_items")]
176-
public static extern ref T[] GetBuffer(List<T> list);
177-
}
178-
179-
#endregion
180-
181160
#endif
182161
}

Ramstack.Structures/Collections/ArrayView`1.cs

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
using System.Collections;
2-
using System.Collections.Immutable;
3-
4-
using Ramstack.Internal;
5-
61
namespace Ramstack.Collections;
72

83
/// <summary>
@@ -201,7 +196,28 @@ public ReadOnlyMemory<T> AsMemory() =>
201196
[MethodImpl(MethodImplOptions.AggressiveInlining)]
202197
public ref readonly T GetPinnableReference()
203198
{
204-
// To match the behavior of ReadOnlySpan<T>
199+
//
200+
// Normalize the returned reference.
201+
// We must not return a reference to an element outside the bounds of our view
202+
// when the view is empty (_count = 0), even if it points to a valid element
203+
// in the underlying array. In this case, we return a null reference.
204+
//
205+
// Examples:
206+
//
207+
// Full array (_array):
208+
// [0][1][2][3][4][5][6][7][8][9]
209+
// |--------------------------|
210+
//
211+
// Case 1: Non-empty view (_index = 3, _count = 4)
212+
// [3][4][5][6]
213+
// ^ ^
214+
// |--------| <- valid range for this view
215+
//
216+
// Case 2: Empty view (_index = 3, _count = 0)
217+
// [3][4][5][6]
218+
// ^
219+
// | <- no valid reference for this view
220+
//
205221

206222
ref var p = ref Unsafe.NullRef<T>();
207223

@@ -219,8 +235,9 @@ public void CopyTo(Span<T> destination) =>
219235
AsSpan().CopyTo(destination);
220236

221237
/// <summary>
222-
/// Attempts to copy the contents of this <see cref="ArrayView{T}"/> into a destination <see cref="Span{T}"/>
223-
/// and returns a value to indicate whether the operation succeeded.
238+
/// Attempts to copy the contents of this <see cref="ArrayView{T}"/>
239+
/// into a destination <see cref="Span{T}"/> and returns a value to indicate
240+
/// whether the operation succeeded.
224241
/// </summary>
225242
/// <param name="destination">The span to copy items into.</param>
226243
/// <returns>
@@ -302,7 +319,8 @@ public static implicit operator ArrayView<T>(ArraySegment<T> segment) =>
302319
new(segment.Array!, segment.Offset, segment.Count, dummy: 0);
303320

304321
/// <summary>
305-
/// Returns a string representation of the current instance's state, intended for debugging purposes.
322+
/// Returns a string representation of the current instance's state,
323+
/// intended for debugging purposes.
306324
/// </summary>
307325
/// <returns>
308326
/// A string containing information about the current instance.
@@ -363,9 +381,9 @@ public readonly ref readonly T Current
363381
[MethodImpl(MethodImplOptions.AggressiveInlining)]
364382
internal Enumerator(ArrayView<T> view)
365383
{
366-
_array = view._array;
367384
_index = view._index - 1;
368385
_final = view._index + view._count;
386+
_array = view._array;
369387
}
370388

371389
/// <inheritdoc cref="IEnumerator.MoveNext" />
@@ -408,9 +426,9 @@ public T Current
408426
/// <param name="view">The <see cref="ArrayView{T}"/> to iterate through its elements.</param>
409427
public ArrayViewEnumerator(ArrayView<T> view)
410428
{
411-
_array = view._array;
412429
_index = view._index - 1;
413430
_final = view._index + view._count;
431+
_array = view._array;
414432
}
415433

416434
/// <inheritdoc />

Ramstack.Structures/Collections/ImmutableArrayExtensions.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
using System.Collections.Immutable;
2-
31
namespace Ramstack.Collections;
42

53
/// <summary>

Ramstack.Structures/Collections/ReadOnlyArrayExtensions.Linq.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -392,15 +392,22 @@ public static T[] ToArray<T>(this ReadOnlyArray<T> source)
392392
/// <inheritdoc cref="Enumerable.ToList{TSource}"/>
393393
public static List<T> ToList<T>(this ReadOnlyArray<T> source)
394394
{
395-
#if NET8_0_OR_GREATER
395+
#if NET9_0_OR_GREATER
396+
var list = new List<T>();
397+
398+
ListAccessor<T>.GetArray(list) = source.ToArray();
399+
ListAccessor<T>.GetCount(list) = source.Length;
400+
401+
return list;
402+
#elif NET8_0_OR_GREATER
396403
var list = new List<T>(source.Length);
397404

398405
System.Runtime.InteropServices.CollectionsMarshal.SetCount(list, source.Length);
399-
source.CopyTo(System.Runtime.InteropServices.CollectionsMarshal.AsSpan(list));
406+
source.TryCopyTo(System.Runtime.InteropServices.CollectionsMarshal.AsSpan(list));
400407

401408
return list;
402409
#else
403-
return source.Inner!.ToList();
410+
return [..source.Inner!];
404411
#endif
405412
}
406413

Ramstack.Structures/Collections/ReadOnlyArrayExtensions.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
using System.Collections.Immutable;
2-
31
namespace Ramstack.Collections;
42

53
/// <summary>
@@ -70,19 +68,22 @@ public static ReadOnlyArray<T> ToReadOnlyArray<T>([InstantHandle] this IEnumerab
7068
/// <param name="array">The <see cref="ReadOnlyArray{T}"/> instance.</param>
7169
/// <param name="value">The object to locate in array.</param>
7270
/// <returns>
73-
/// The zero-based index of the first occurrence of <paramref name="value"/> in the <paramref name="array"/>, if found; otherwise, <c>-1</c>.
71+
/// The zero-based index of the first occurrence of <paramref name="value"/>
72+
/// in the <paramref name="array"/>, if found; otherwise, <c>-1</c>.
7473
/// </returns>
7574
public static int IndexOf<T>(this ReadOnlyArray<T> array, T value) =>
7675
Array.IndexOf(array.Inner!, value);
7776

7877
/// <summary>
79-
/// Searches for the specified object and returns the index of the last occurrence within the entire <see cref="ReadOnlyArray{T}"/>.
78+
/// Searches for the specified object and returns the index
79+
/// of the last occurrence within the entire <see cref="ReadOnlyArray{T}"/>.
8080
/// </summary>
8181
/// <typeparam name="T">The type of element in the array.</typeparam>
8282
/// <param name="array">The <see cref="ReadOnlyArray{T}"/> instance.</param>
8383
/// <param name="value">The object to locate in array.</param>
8484
/// <returns>
85-
/// The zero-based index of the last occurrence of <paramref name="value"/> in the <paramref name="array"/>, if found; otherwise, <c>-1</c>.
85+
/// The zero-based index of the last occurrence of <paramref name="value"/>
86+
/// in the <paramref name="array"/>, if found; otherwise, <c>-1</c>.
8687
/// </returns>
8788
public static int LastIndexOf<T>(this ReadOnlyArray<T> array, T value) =>
8889
Array.LastIndexOf(array.Inner!, value);

Ramstack.Structures/Collections/ReadOnlyArray`1.cs

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
using System.Collections;
2-
using System.Collections.Immutable;
3-
41
namespace Ramstack.Collections;
52

63
/// <summary>
@@ -102,7 +99,8 @@ public ReadOnlyArray(T item1, T item2, T item3, T item4) =>
10299
public ReadOnlyArray(ReadOnlySpan<T> items)
103100
{
104101
//
105-
// Avoid address exposure in cases where the destination local does not actually end up escaping in any way.
102+
// Avoid address exposure in cases where the destination local
103+
// does not actually end up escaping in any way.
106104
// https://github.com/dotnet/runtime/pull/102808
107105
//
108106

@@ -133,7 +131,8 @@ public Enumerator GetEnumerator()
133131
/// </summary>
134132
/// <param name="start">The index at which to begin the slice.</param>
135133
/// <returns>
136-
/// A readonly span that consists of all elements of the current array from <paramref name="start"/> to the end.
134+
/// A readonly span that consists of all elements of the current array
135+
/// from <paramref name="start"/> to the end.
137136
/// </returns>
138137
[MethodImpl(MethodImplOptions.AggressiveInlining)]
139138
public ReadOnlyArray<T> Slice(int start)
@@ -152,7 +151,8 @@ public ReadOnlyArray<T> Slice(int start)
152151
/// <param name="start">The index at which to begin the slice.</param>
153152
/// <param name="length">The desired length for the slice.</param>
154153
/// <returns>
155-
/// A readonly span that consists of <paramref name="length"/> elements from the current array starting at <paramref name="start"/>.
154+
/// A readonly span that consists of <paramref name="length"/> elements
155+
/// from the current array starting at <paramref name="start"/>.
156156
/// </returns>
157157
[MethodImpl(MethodImplOptions.AggressiveInlining)]
158158
public ReadOnlyArray<T> Slice(int start, int length)
@@ -188,7 +188,8 @@ public ReadOnlySpan<T> AsSpan() =>
188188
new(Inner ?? []);
189189

190190
/// <summary>
191-
/// Creates a new read-only span over a portion of the current array starting at a specified position to the end of the array.
191+
/// Creates a new read-only span over a portion of the current array
192+
/// starting at a specified position to the end of the array.
192193
/// </summary>
193194
/// <param name="start">The index at which to begin the span.</param>
194195
/// <returns>
@@ -199,7 +200,8 @@ public ReadOnlySpan<T> AsSpan(int start) =>
199200
AsSpan().Slice(start);
200201

201202
/// <summary>
202-
/// Creates a new read-only span over a portion of the current array starting at a specified position for a specified length.
203+
/// Creates a new read-only span over a portion of the current array
204+
/// starting at a specified position for a specified length.
203205
/// </summary>
204206
/// <param name="start">The index at which to begin the span.</param>
205207
/// <param name="length">The number of items in the span.</param>
@@ -219,7 +221,8 @@ public ArrayView<T> AsView() =>
219221
new(Inner ?? []);
220222

221223
/// <summary>
222-
/// Creates an <see cref="ArrayView{T}"/> over the current array starting at a specified position to the end of the array.
224+
/// Creates an <see cref="ArrayView{T}"/> over the current array
225+
/// starting at a specified position to the end of the array.
223226
/// </summary>
224227
/// <param name="index">The index at which to begin the array view.</param>
225228
/// <returns>
@@ -230,7 +233,8 @@ public ArrayView<T> AsView(int index) =>
230233
AsView().Slice(index);
231234

232235
/// <summary>
233-
/// Creates an <see cref="ArrayView{T}"/> over the current array starting at a specified position for a specified length.
236+
/// Creates an <see cref="ArrayView{T}"/> over the current array
237+
/// starting at a specified position for a specified length.
234238
/// </summary>
235239
/// <param name="index">The index at which to begin the array view.</param>
236240
/// <param name="count">The number of items in the array view.</param>
@@ -253,7 +257,8 @@ public ReadOnlyMemory<T> AsMemory() =>
253257
/// Returns a reference to the element of the <see cref="ReadOnlyArray{T}"/> at index zero.
254258
/// </summary>
255259
/// <returns>
256-
/// A reference to the element of the <see cref="ReadOnlyArray{T}"/> at index zero, or <see langword="null"/> if <see cref="IsDefault"/> is true.
260+
/// A reference to the element of the <see cref="ReadOnlyArray{T}"/> at index zero,
261+
/// or <see langword="null"/> if <see cref="IsDefault"/> is true.
257262
/// </returns>
258263
public ref readonly T GetPinnableReference() =>
259264
ref MemoryMarshal.GetArrayDataReference(Inner!);
@@ -492,15 +497,16 @@ public readonly ref readonly T Current
492497
[MethodImpl(MethodImplOptions.AggressiveInlining)]
493498
internal Enumerator(T[] array)
494499
{
495-
_array = array;
496500
_index = -1;
501+
_array = array;
497502
}
498503

499504
/// <summary>
500505
/// Advances the enumerator to the next element of the collection.
501506
/// </summary>
502507
/// <returns>
503-
/// <see langword="true"/> if the enumerator was successfully advanced to the next element; otherwise, <see langword="false"/>.
508+
/// <see langword="true"/> if the enumerator was successfully advanced to the next element;
509+
/// otherwise, <see langword="false"/>.
504510
/// </returns>
505511
[MethodImpl(MethodImplOptions.AggressiveInlining)]
506512
public bool MoveNext() =>

Ramstack.Structures/Internal/Jetbrains.Assertions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ namespace JetBrains.Annotations;
4646
/// If <see cref="RequireAwait"/> is true, the attribute will only take effect if the method invocation is located under the 'await' expression.
4747
/// </summary>
4848
[AttributeUsage(AttributeTargets.Parameter)]
49+
[Conditional("JETBRAINS_ANNOTATIONS")]
4950
internal sealed class InstantHandleAttribute : Attribute
5051
{
5152
/// <summary>
@@ -61,4 +62,5 @@ internal sealed class InstantHandleAttribute : Attribute
6162
/// of delegate type by analyzing LINQ method chains.
6263
/// </summary>
6364
[AttributeUsage(AttributeTargets.Method)]
65+
[Conditional("JETBRAINS_ANNOTATIONS")]
6466
internal sealed class LinqTunnelAttribute : Attribute { }
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
namespace Ramstack.Internal;
2+
3+
#if NET9_0_OR_GREATER
4+
5+
/// <summary>
6+
/// Provides low-level access to the internal array buffer of a <see cref="List{T}"/>.
7+
/// </summary>
8+
/// <typeparam name="T">The type of the elements in the list.</typeparam>
9+
internal static class ListAccessor<T>
10+
{
11+
/// <summary>
12+
/// Returns a reference to the internal array buffer of the specified <see cref="List{T}"/>.
13+
/// </summary>
14+
/// <param name="list">The list whose internal buffer is to be accessed.</param>
15+
/// <returns>
16+
/// A reference to the internal array used by the list.
17+
/// </returns>
18+
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_items")]
19+
public static extern ref T[] GetArray(List<T> list);
20+
21+
/// <summary>
22+
/// Returns a reference to the internal "_size" field of the specified <see cref="List{T}"/>
23+
/// representing the number of elements in the list.
24+
/// </summary>
25+
/// <param name="list">The list whose internal "_size" field is to be accessed.</param>
26+
/// <returns>
27+
/// A reference to the internal "_size" field representing the number of elements in the list.
28+
/// </returns>
29+
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_size")]
30+
public static extern ref int GetCount(List<T> list);
31+
}
32+
33+
#endif

Ramstack.Structures/Ramstack.Structures.csproj

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,17 @@ Ramstack.Collections.ReadOnlyArray&lt;T&gt;</Description>
4040
</PropertyGroup>
4141

4242
<ItemGroup>
43+
<Using Include="JetBrains.Annotations" />
44+
<Using Include="Ramstack.Internal" />
45+
<Using Include="System.Collections" />
46+
<Using Include="System.Collections.Generic" />
47+
<Using Include="System.Collections.Immutable" />
4348
<Using Include="System.ComponentModel" />
4449
<Using Include="System.Diagnostics" />
4550
<Using Include="System.Diagnostics.CodeAnalysis" />
4651
<Using Include="System.Globalization" />
4752
<Using Include="System.Runtime.CompilerServices" />
4853
<Using Include="System.Runtime.InteropServices" />
49-
<Using Include="JetBrains.Annotations" />
5054
</ItemGroup>
5155

5256
<ItemGroup>

0 commit comments

Comments
 (0)