Skip to content

Commit d457663

Browse files
committed
Add initial implemenation of Span<> class
1 parent c6f3b88 commit d457663

File tree

5 files changed

+466
-29
lines changed

5 files changed

+466
-29
lines changed

Tests/NFUnitTestTypes/UnitTestsSpanByte.cs

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,67 +13,67 @@ public class UnitTestsSpanByte
1313
public void EmptySpanTests()
1414
{
1515
// Empty span
16-
SpanByte span = SpanByte.Empty;
16+
Span span = Span.Empty;
1717
// Create a destination span larger
18-
SpanByte destination = new byte[1];
18+
Span destination = new byte[1];
1919
span.CopyTo(destination);
2020

2121
// Now also empty
22-
destination = SpanByte.Empty;
22+
destination = Span.Empty;
2323
span.CopyTo(destination);
2424
}
2525

2626
[TestMethod]
2727
public void RaisingExceptionsOfAllKindsTests()
2828
{
2929
// Should raise an exception on creation
30-
Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { SpanByte span = new SpanByte(null, 1, 2); }, "ArgumentOutOfRangeException when array is null, start is 1 and length is 2");
31-
Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { SpanByte span = new SpanByte(new byte[1], 1, 2); }, "ArgumentOutOfRangeException when array is new byte[1], start is 1 and length is 2");
32-
Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { SpanByte span = new SpanByte(new byte[1], 0, 2); }, "ArgumentOutOfRangeException when array is new byte[1], start is 0 and length is 2");
33-
Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { SpanByte span = new SpanByte(new byte[1], 2, 0); }, "ArgumentOutOfRangeException when array is new byte[1], start is 2 and length is 0");
30+
Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { Span span = new Span(null, 1, 2); }, "ArgumentOutOfRangeException when array is null, start is 1 and length is 2");
31+
Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { Span span = new Span(new byte[1], 1, 2); }, "ArgumentOutOfRangeException when array is new byte[1], start is 1 and length is 2");
32+
Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { Span span = new Span(new byte[1], 0, 2); }, "ArgumentOutOfRangeException when array is new byte[1], start is 0 and length is 2");
33+
Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { Span span = new Span(new byte[1], 2, 0); }, "ArgumentOutOfRangeException when array is new byte[1], start is 2 and length is 0");
3434

3535
// Exception on index access
3636
byte[] array = new byte[16] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F };
3737
Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () =>
3838
{
39-
SpanByte span = new SpanByte(array);
39+
Span span = new Span(array);
4040
var data = span[span.Length];
4141
});
4242
Assert.ThrowsException(typeof(IndexOutOfRangeException), () =>
4343
{
44-
SpanByte span = new SpanByte(array);
44+
Span span = new Span(array);
4545
var data = span[-1];
4646
});
4747

4848
// Copy to with too small destination
4949
Assert.ThrowsException(typeof(ArgumentException), () =>
5050
{
51-
SpanByte span = new SpanByte(array);
52-
SpanByte destination = new byte[span.Length - 1];
51+
Span span = new Span(array);
52+
Span destination = new byte[span.Length - 1];
5353
span.CopyTo(destination);
5454
});
5555

5656
// Slicing arguments
5757
Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () =>
5858
{
59-
SpanByte span = new SpanByte(array);
59+
Span span = new Span(array);
6060
var sliced = span.Slice(span.Length + 1);
6161
});
6262
Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () =>
6363
{
64-
SpanByte span = new SpanByte(array);
64+
Span span = new Span(array);
6565
var sliced = span.Slice(1, span.Length);
6666
});
6767

6868
Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () =>
6969
{
70-
SpanByte span = new SpanByte(array);
70+
Span span = new Span(array);
7171
var sliced = span.Slice(-1, span.Length);
7272
});
7373

7474
Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () =>
7575
{
76-
SpanByte span = new SpanByte(array);
76+
Span span = new Span(array);
7777
var sliced = span.Slice(1, -1);
7878
});
7979

@@ -84,7 +84,7 @@ public void ToArrayTest()
8484
{
8585
byte[] array = new byte[16] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F };
8686

87-
SpanByte span = new(array);
87+
Span span = new(array);
8888

8989
byte[] toArray = span.ToArray();
9090

@@ -95,28 +95,28 @@ public void ToArrayTest()
9595
public void ConstructorsOfAllKindsTests()
9696
{
9797
// Empty span
98-
SpanByte span = new SpanByte();
98+
Span span = new Span();
9999
Assert.AreEqual(span.Length, 0, "Empty SpanByte should have length of 0");
100100
Assert.IsTrue(span.IsEmpty, "Empty SpanByte should be IsEmpty");
101101

102102
// Empty span
103-
span = new SpanByte(null, 0, 0);
103+
span = new Span(null, 0, 0);
104104
Assert.AreEqual(span.Length, 0, "Empty SpanByte should have length of 0");
105105
Assert.IsTrue(span.IsEmpty, "Empty SpanByte should be IsEmpty");
106106

107107
// Empty span
108-
span = SpanByte.Empty;
108+
span = Span.Empty;
109109
Assert.AreEqual(span.Length, 0, "Empty SpanByte should have length of 0");
110110
Assert.IsTrue(span.IsEmpty, "Empty SpanByte should be IsEmpty");
111111

112112
// Span from normal array
113113
byte[] array = new byte[16] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F };
114-
span = new SpanByte(array);
114+
span = new Span(array);
115115
Assert.AreEqual(span.Length, array.Length, $"SpanByte should have length of the array it takes: {array.Length}");
116116
Assert.IsFalse(span.IsEmpty, "SpanByte should NOT be IsEmpty");
117117

118118
// Span from normal array with different start and length
119-
span = new SpanByte(array, 2, 8);
119+
span = new Span(array, 2, 8);
120120
Assert.AreEqual(span.Length, 8, $"SpanByte should have length of 8");
121121
Assert.IsFalse(span.IsEmpty, "SpanByte should NOT be IsEmpty");
122122
}
@@ -126,7 +126,7 @@ public void SliceTests()
126126
{
127127
// Span from normal array
128128
byte[] array = new byte[16] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F };
129-
SpanByte span = new SpanByte(array);
129+
Span span = new Span(array);
130130
// Slice 2 elements and check
131131
var sliced = span.Slice(0, 2);
132132
Assert.AreEqual(sliced.Length, 2, "Sliced span lenght must be 2");
@@ -167,9 +167,9 @@ public void SliceTests()
167167
public void CopyToTests()
168168
{
169169
byte[] array = new byte[16] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F };
170-
SpanByte span = new SpanByte(array);
170+
Span span = new Span(array);
171171
// First a copy to with the full span
172-
SpanByte toCopy = new byte[span.Length];
172+
Span toCopy = new byte[span.Length];
173173
span.CopyTo(toCopy);
174174
for (int i = 0; i < span.Length; i++)
175175
{
@@ -192,14 +192,14 @@ public void GetElementsTests()
192192
{
193193
// Span from normal array
194194
byte[] array = new byte[16] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F };
195-
SpanByte span = new SpanByte(array);
195+
Span span = new Span(array);
196196
for (int i = 0; i < span.Length; i++)
197197
{
198198
Assert.AreEqual(span[i], array[i], "SpanByte value should be the same as from the original array");
199199
}
200200

201201
// Partial span
202-
span = new SpanByte(array, 2, 8);
202+
span = new Span(array, 2, 8);
203203
for (int i = 0; i < span.Length; i++)
204204
{
205205
Assert.AreEqual(span[i], array[i + 2], "SpanByte value should be the same as from the original array");
@@ -210,7 +210,7 @@ public void GetElementsTests()
210210
public void SetElementsTests()
211211
{
212212
// Create a span, and set the data
213-
SpanByte span = new byte[12];
213+
Span span = new byte[12];
214214
// All should be 0
215215
for (int i = 0; i < span.Length; i++)
216216
{

nanoFramework.CoreLibrary/CoreLibrary.nfproj

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,9 @@
191191
<Compile Include="System\SByte.cs" />
192192
<Compile Include="System\SerializableAttribute.cs" />
193193
<Compile Include="System\Single.cs" />
194-
<Compile Include="System\SpanByte.cs" />
194+
<Compile Include="System\ReadOnlySpan.cs" />
195+
<Compile Include="System\SpanDebugView.cs" />
196+
<Compile Include="System\Span.cs" />
195197
<Compile Include="System\String.cs" />
196198
<Compile Include="System\SystemException.cs" />
197199
<Compile Include="System\TargetFrameworkAttribute.cs" />
@@ -254,4 +256,4 @@
254256
<Import Project="..\packages\Microsoft.SourceLink.Common.1.1.1\build\Microsoft.SourceLink.Common.targets" Condition="Exists('..\packages\Microsoft.SourceLink.Common.1.1.1\build\Microsoft.SourceLink.Common.targets')" />
255257
<Import Project="..\packages\Microsoft.SourceLink.GitHub.1.1.1\build\Microsoft.SourceLink.GitHub.targets" Condition="Exists('..\packages\Microsoft.SourceLink.GitHub.1.1.1\build\Microsoft.SourceLink.GitHub.targets')" />
256258
<Import Project="..\packages\Nerdbank.GitVersioning.3.7.115\build\Nerdbank.GitVersioning.targets" Condition="Exists('..\packages\Nerdbank.GitVersioning.3.7.115\build\Nerdbank.GitVersioning.targets')" />
257-
</Project>
259+
</Project>
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Diagnostics;
5+
6+
#pragma warning disable S3928 // Parameter names used into ArgumentException constructors should match an existing one
7+
8+
namespace System
9+
{
10+
/// <summary>
11+
/// <see cref="ReadOnlySpan"/> represents a contiguous region of arbitrary memory. Unlike arrays, it can point to either managed
12+
/// or native memory, or to memory allocated on the stack. It is type-safe and memory-safe.
13+
/// </summary>
14+
[DebuggerTypeProxy(typeof(SpanDebugView<>))]
15+
[DebuggerDisplay("{ToString(),raw}")]
16+
public readonly ref struct ReadOnlySpan<T>
17+
{
18+
private readonly T[] _array;
19+
private readonly int _start;
20+
private readonly int _length;
21+
22+
/// <summary>
23+
/// Creates a new read-only span over the entirety of the target array.
24+
/// </summary>
25+
/// <param name="array">The target array.</param>
26+
/// <remarks>Returns default when <paramref name="array"/> is null.</remarks>
27+
public ReadOnlySpan(T[]? array)
28+
{
29+
_array = array ?? Array.Empty<T>();
30+
_start = 0;
31+
_length = _array.Length;
32+
}
33+
34+
/// <summary>
35+
/// Creates a new read-only span over the portion of the target array beginning
36+
/// at 'start' index and ending at 'end' index (exclusive).
37+
/// </summary>
38+
/// <param name="array">The target array.</param>
39+
/// <param name="start">The zero-based index at which to begin the read-only span.</param>
40+
/// <param name="length">The number of items in the read-only span.</param>
41+
/// <remarks>Returns default when <paramref name="array"/> is null.</remarks>
42+
/// <exception cref="ArgumentOutOfRangeException">
43+
/// Thrown when the specified <paramref name="start"/> or end index is not in the range (&lt;0 or &gt;Length).
44+
/// </exception>
45+
public ReadOnlySpan(T[]? array, int start, int length)
46+
{
47+
if (array == null)
48+
{
49+
if (start != 0 || length != 0)
50+
{
51+
throw new ArgumentOutOfRangeException();
52+
}
53+
54+
_array = Array.Empty<T>();
55+
_start = 0;
56+
_length = 0;
57+
58+
return;
59+
}
60+
61+
if ((uint)start > (uint)array.Length
62+
|| (uint)length > (uint)(array.Length - start))
63+
{
64+
throw new ArgumentOutOfRangeException();
65+
}
66+
67+
_array = array;
68+
_start = start;
69+
_length = length;
70+
}
71+
72+
/// <summary>
73+
/// Returns the specified element of the read-only span.
74+
/// </summary>
75+
/// <param name="index">The zero-based index.</param>
76+
/// <returns></returns>
77+
/// <exception cref="IndexOutOfRangeException">
78+
/// Thrown when index less than 0 or index greater than or equal to Length
79+
/// </exception>
80+
public ref readonly T this[int index]
81+
{
82+
get
83+
{
84+
if ((uint)index >= (uint)_length)
85+
{
86+
throw new ArgumentOutOfRangeException();
87+
}
88+
89+
return ref _array[_start + index];
90+
}
91+
}
92+
93+
/// <summary>
94+
/// The number of items in the read-only span.
95+
/// </summary>
96+
public int Length => _length;
97+
98+
/// <summary>
99+
/// Gets a value indicating whether this <see cref="Span{T}"/> is empty.
100+
/// </summary>
101+
/// <value><see langword="true"/> if this span is empty; otherwise, <see langword="false"/>.</value>
102+
public bool IsEmpty => _length == 0;
103+
104+
/// <summary>
105+
/// Returns false if left and right point at the same memory and have the same length. Note that
106+
/// this does *not* check to see if the *contents* are equal.
107+
/// </summary>
108+
public static bool operator !=(ReadOnlySpan<T> left, ReadOnlySpan<T> right) => !(left == right);
109+
110+
/// <summary>
111+
/// Defines an implicit conversion of an array to a <see cref="ReadOnlySpan{T}"/>
112+
/// </summary>
113+
public static implicit operator ReadOnlySpan<T>(T[]? array) => new ReadOnlySpan<T>(array);
114+
115+
/// <summary>
116+
/// Returns true if left and right point at the same memory and have the same length. Note that
117+
/// this does *not* check to see if the *contents* are equal.
118+
/// </summary>
119+
public static bool operator ==(ReadOnlySpan<T> left, ReadOnlySpan<T> right)
120+
{
121+
if (left.Length != right.Length)
122+
{
123+
return false;
124+
}
125+
126+
for (int i = 0; i < left.Length; i++)
127+
{
128+
if (!Equals(left[i], right[i]))
129+
{
130+
return false;
131+
}
132+
}
133+
134+
return true;
135+
}
136+
137+
/// <summary>
138+
/// Copies the contents of this read-only span into a new array. This heap
139+
/// allocates, so should generally be avoided, however it is sometimes
140+
/// necessary to bridge the gap with APIs written in terms of arrays.
141+
/// </summary>
142+
public T[] ToArray()
143+
{
144+
T[] destination = new T[_length];
145+
Array.Copy(_array, _start, destination, 0, _length);
146+
return destination;
147+
}
148+
}
149+
}
150+
151+
#pragma warning restore S3928 // Parameter names used into ArgumentException constructors should match an existing one

0 commit comments

Comments
 (0)