diff --git a/Tests/NFUnitTestTypes/NFUnitTestTypes.nfproj b/Tests/NFUnitTestTypes/NFUnitTestTypes.nfproj index 4b9ae62..f6f14b4 100644 --- a/Tests/NFUnitTestTypes/NFUnitTestTypes.nfproj +++ b/Tests/NFUnitTestTypes/NFUnitTestTypes.nfproj @@ -26,6 +26,7 @@ + diff --git a/Tests/NFUnitTestTypes/UnitTestsReadOnlySpanByte.cs b/Tests/NFUnitTestTypes/UnitTestsReadOnlySpanByte.cs new file mode 100644 index 0000000..367cd0d --- /dev/null +++ b/Tests/NFUnitTestTypes/UnitTestsReadOnlySpanByte.cs @@ -0,0 +1,351 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using nanoFramework.TestFramework; + +namespace NFUnitTestTypes +{ + [TestClass] + public class UnitTestsReadOnlySpanByte + { + [TestMethod] + public void EmptySpanTests() + { + // Empty span using default constructor + ReadOnlySpan span = new ReadOnlySpan(); + + Assert.AreEqual(0, span.Length, "Empty ReadOnlySpan should have length of 0"); + Assert.IsTrue(span.IsEmpty, "Empty ReadOnlySpan should be IsEmpty"); + + // Empty span from null array + span = new ReadOnlySpan(null); + + Assert.AreEqual(0, span.Length, "ReadOnlySpan from null should have length of 0"); + Assert.IsTrue(span.IsEmpty, "ReadOnlySpan from null should be IsEmpty"); + + // Empty span using Empty property + span = ReadOnlySpan.Empty; + + Assert.AreEqual(0, span.Length, "Empty ReadOnlySpan should have length of 0"); + Assert.IsTrue(span.IsEmpty, "Empty ReadOnlySpan should be IsEmpty"); + } + + [TestMethod] + public void RaisingExceptionsOfAllKindsTests() + { + // Should raise an exception on creation + Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { ReadOnlySpan span = new ReadOnlySpan(null, 1, 2); }, "ArgumentOutOfRangeException when array is null, start is 1 and length is 2"); + Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { ReadOnlySpan span = new ReadOnlySpan(new byte[1], 1, 2); }, "ArgumentOutOfRangeException when array is new byte[1], start is 1 and length is 2"); + Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { ReadOnlySpan span = new ReadOnlySpan(new byte[1], 0, 2); }, "ArgumentOutOfRangeException when array is new byte[1], start is 0 and length is 2"); + Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => { ReadOnlySpan span = new ReadOnlySpan(new byte[1], 2, 0); }, "ArgumentOutOfRangeException when array is new byte[1], start is 2 and length is 0"); + + // Exception on index access + byte[] array = new byte[16] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; + Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => + { + ReadOnlySpan span = new ReadOnlySpan(array); + _ = span[span.Length]; + }); + + Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => + { + ReadOnlySpan span = new ReadOnlySpan(array); + _ = span[-1]; + }); + + // Slicing arguments + Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => + { + ReadOnlySpan span = new ReadOnlySpan(array); + _ = span.Slice(span.Length + 1); + }); + + Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => + { + ReadOnlySpan span = new ReadOnlySpan(array); + _ = span.Slice(1, span.Length); + }); + + Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => + { + ReadOnlySpan span = new ReadOnlySpan(array); + _ = span.Slice(-1, span.Length); + }); + + Assert.ThrowsException(typeof(ArgumentOutOfRangeException), () => + { + ReadOnlySpan span = new ReadOnlySpan(array); + _ = span.Slice(1, -1); + }); + } + + [TestMethod] + public void ToArrayTest() + { + byte[] array = new byte[16] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; + + ReadOnlySpan span = new ReadOnlySpan(array); + + byte[] toArray = span.ToArray(); + + CollectionAssert.AreEqual(array, toArray, "Original array and ReadOnlySpan.ToArray should be the same"); + } + + [TestMethod] + public void ConstructorsOfAllKindsTests() + { + // Empty span using default constructor + ReadOnlySpan span = new ReadOnlySpan(); + + Assert.AreEqual(span.Length, 0, "Empty ReadOnlySpan should have length of 0"); + Assert.IsTrue(span.IsEmpty, "Empty ReadOnlySpan should be IsEmpty"); + + // Empty span from null with zero start and length + span = new ReadOnlySpan(null, 0, 0); + + Assert.AreEqual(span.Length, 0, "Empty ReadOnlySpan should have length of 0"); + Assert.IsTrue(span.IsEmpty, "Empty ReadOnlySpan should be IsEmpty"); + + // Empty span using Empty property + span = ReadOnlySpan.Empty; + + Assert.AreEqual(span.Length, 0, "Empty ReadOnlySpan should have length of 0"); + Assert.IsTrue(span.IsEmpty, "Empty ReadOnlySpan should be IsEmpty"); + + // ReadOnlySpan from normal array + byte[] array = new byte[16] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; + span = new ReadOnlySpan(array); + + Assert.AreEqual(span.Length, array.Length, $"ReadOnlySpan should have length of the array it takes: {array.Length}"); + Assert.IsFalse(span.IsEmpty, "ReadOnlySpan should NOT be IsEmpty"); + + // ReadOnlySpan from normal array with different start and length + span = new ReadOnlySpan(array, 2, 8); + + Assert.AreEqual(span.Length, 8, $"ReadOnlySpan should have length of 8"); + Assert.IsFalse(span.IsEmpty, "ReadOnlySpan should NOT be IsEmpty"); + } + + [TestMethod] + public void SliceTests() + { + // ReadOnlySpan from normal array + byte[] array = new byte[16] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; + ReadOnlySpan span = new ReadOnlySpan(array); + + // Slice 2 elements and check + ReadOnlySpan sliced = span.Slice(0, 2); + + Assert.AreEqual(sliced.Length, 2, "Sliced span length must be 2"); + Assert.AreEqual(sliced[0], (byte)0x00, "Sliced first element must be value 0"); + Assert.AreEqual(sliced[1], (byte)0x01, "Sliced second element must be value 1"); + + // Slice 4 elements starting at index 2 and check + sliced = span.Slice(2, 4); + + Assert.AreEqual(sliced.Length, 4, "Sliced span length must be 4"); + Assert.AreEqual(sliced[0], (byte)0x02, "Sliced first element must be value 2"); + Assert.AreEqual(sliced[1], (byte)0x03, "Sliced second element must be value 3"); + Assert.AreEqual(sliced[2], (byte)0x04, "Sliced third element must be value 4"); + Assert.AreEqual(sliced[3], (byte)0x05, "Sliced fourth element must be value 5"); + + // Slice starting at element 4 and check + sliced = span.Slice(4); + + Assert.AreEqual(sliced.Length, 12, "Sliced span length must be 12"); + + for (int i = 0; i < sliced.Length; i++) + { + Assert.AreEqual(sliced[i], span[i + 4], "ReadOnlySpan value should be the same as from the original span"); + } + + // Slice of slice + ReadOnlySpan secondSliced = sliced.Slice(2, 4); + + Assert.AreEqual(secondSliced.Length, 4, "Sliced span length must be 4"); + + for (int i = 0; i < secondSliced.Length; i++) + { + Assert.AreEqual(secondSliced[i], sliced[i + 2], "ReadOnlySpan value should be the same as from the original span"); + } + + // Should be an empty one + ReadOnlySpan empty = span.Slice(span.Length); + + Assert.AreEqual(empty.Length, 0, "Slicing all the span should result in an empty span"); + Assert.IsTrue(empty.IsEmpty, "Empty span should be empty"); + } + + [TestMethod] + public void GetElementsTests() + { + // ReadOnlySpan from normal array + byte[] array = new byte[16] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; + ReadOnlySpan span = new ReadOnlySpan(array); + for (int i = 0; i < span.Length; i++) + { + Assert.AreEqual(span[i], array[i], "ReadOnlySpan value should be the same as from the original array"); + } + + // Partial span + span = new ReadOnlySpan(array, 2, 8); + for (int i = 0; i < span.Length; i++) + { + Assert.AreEqual(span[i], array[i + 2], "ReadOnlySpan value should be the same as from the original array"); + } + } + + [TestMethod] + public void StackAllocReadOnlySpanTests() + { + // Create a ReadOnlySpan from stack-allocated memory + ReadOnlySpan span = stackalloc byte[16]; + + Assert.AreEqual(16, span.Length, "Stack-allocated ReadOnlySpan should have length of 16"); + Assert.IsFalse(span.IsEmpty, "Stack-allocated ReadOnlySpan should NOT be IsEmpty"); + + // Verify all elements are initialized to zero + for (int i = 0; i < span.Length; i++) + { + Assert.AreEqual((byte)0, span[i], "Stack-allocated ReadOnlySpan elements should be initialized to 0"); + } + } + + [TestMethod] + public void StackAllocReadOnlySpanWithInitializerTests() + { + // Create a ReadOnlySpan from stack-allocated memory with initializer + ReadOnlySpan span = stackalloc byte[8] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; + + Assert.AreEqual(8, span.Length, "Stack-allocated ReadOnlySpan with initializer should have length of 8"); + Assert.IsFalse(span.IsEmpty, "Stack-allocated ReadOnlySpan should NOT be IsEmpty"); + + // Verify the initialized values + for (int i = 0; i < span.Length; i++) + { + Assert.AreEqual((byte)(i + 1), span[i], $"Stack-allocated ReadOnlySpan element at index {i} should be {i + 1}"); + } + } + + [TestMethod] + public void StackAllocReadOnlySpanSliceTests() + { + // Create a ReadOnlySpan from stack-allocated memory + ReadOnlySpan span = stackalloc byte[16] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; + + // Slice the stack-allocated span + ReadOnlySpan sliced = span.Slice(4, 8); + + Assert.AreEqual(8, sliced.Length, "Sliced stack-allocated ReadOnlySpan should have length of 8"); + + for (int i = 0; i < sliced.Length; i++) + { + Assert.AreEqual((byte)(i + 4), sliced[i], $"Sliced element at index {i} should be {i + 4}"); + } + } + + [TestMethod] + public void StackAllocReadOnlySpanToArrayTests() + { + // Create a ReadOnlySpan from stack-allocated memory + ReadOnlySpan span = stackalloc byte[6] { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF }; + + // Convert to array + byte[] array = span.ToArray(); + + Assert.AreEqual(6, array.Length, "ToArray should create an array with the same length"); + + byte[] expected = new byte[] { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF }; + CollectionAssert.AreEqual(expected, array, "Stack-allocated ReadOnlySpan ToArray should match expected values"); + } + + [TestMethod] + public void StackAllocEmptyReadOnlySpanTests() + { + // Create an empty stack-allocated ReadOnlySpan + ReadOnlySpan span = stackalloc byte[0]; + + Assert.AreEqual(0, span.Length, "Empty stack-allocated ReadOnlySpan should have length of 0"); + Assert.IsTrue(span.IsEmpty, "Empty stack-allocated ReadOnlySpan should be IsEmpty"); + } + + [TestMethod] + public void ImplicitConversionFromArrayTests() + { + // Test implicit conversion from array to ReadOnlySpan + byte[] array = new byte[8] { 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80 }; + + ReadOnlySpan span = array; // Implicit conversion + + Assert.AreEqual(array.Length, span.Length, "Implicitly converted ReadOnlySpan should have same length as array"); + + for (int i = 0; i < array.Length; i++) + { + Assert.AreEqual(array[i], span[i], $"Element at index {i} should match"); + } + } + + [TestMethod] + public void EqualityOperatorTests() + { + byte[] array1 = new byte[4] { 0x01, 0x02, 0x03, 0x04 }; + byte[] array2 = new byte[4] { 0x01, 0x02, 0x03, 0x04 }; + byte[] array3 = new byte[4] { 0x01, 0x02, 0x03, 0x05 }; + + ReadOnlySpan span1 = new ReadOnlySpan(array1); + ReadOnlySpan span2 = new ReadOnlySpan(array2); + ReadOnlySpan span3 = new ReadOnlySpan(array3); + + // Test equality with same content + Assert.IsTrue(span1 == span2, "ReadOnlySpans with same content should be equal"); + + // Test inequality with different content + Assert.IsTrue(span1 != span3, "ReadOnlySpans with different content should not be equal"); + Assert.IsFalse(span1 == span3, "ReadOnlySpans with different content should not be equal"); + + // Test empty spans + ReadOnlySpan empty1 = new ReadOnlySpan(); + ReadOnlySpan empty2 = new ReadOnlySpan(); + + Assert.IsTrue(empty1 == empty2, "Empty ReadOnlySpans should be equal"); + } + + [TestMethod] + public void ReadOnlyBehaviorTests() + { + // Verify that ReadOnlySpan protects the underlying data from modification through the span + byte[] array = new byte[4] { 0x01, 0x02, 0x03, 0x04 }; + ReadOnlySpan span = new ReadOnlySpan(array); + + // We can read values + Assert.AreEqual((byte)0x01, span[0], "Should be able to read from ReadOnlySpan"); + + // The following would not compile (which is the desired behavior): + // span[0] = 0xFF; // Error: Cannot assign to a readonly ref return + + // But we can still modify the underlying array directly + array[0] = 0xFF; + Assert.AreEqual((byte)0xFF, span[0], "Modifying underlying array should be visible through ReadOnlySpan"); + } + + [TestMethod] + public void PartialSpanTests() + { + byte[] array = new byte[16] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; + + // Create ReadOnlySpan that covers middle portion of array + ReadOnlySpan span = new ReadOnlySpan(array, 4, 8); + + Assert.AreEqual(8, span.Length, "Partial ReadOnlySpan should have correct length"); + Assert.AreEqual((byte)0x04, span[0], "First element should be from start index"); + Assert.AreEqual((byte)0x0B, span[7], "Last element should be at start + length - 1"); + + // Verify all elements in the partial span + for (int i = 0; i < span.Length; i++) + { + Assert.AreEqual(array[i + 4], span[i], $"Element at index {i} should match array element at {i + 4}"); + } + } + } +} diff --git a/Tests/NFUnitTestTypes/UnitTestsSpanByte.cs b/Tests/NFUnitTestTypes/UnitTestsSpanByte.cs index 9ed5dec..7d7e254 100644 --- a/Tests/NFUnitTestTypes/UnitTestsSpanByte.cs +++ b/Tests/NFUnitTestTypes/UnitTestsSpanByte.cs @@ -247,5 +247,121 @@ public void SetElementsTests() Assert.AreEqual(span[i], (byte)i, "SpanByte value should be the same as setup in the set method"); } } + + [TestMethod] + public void StackAllocSpanTests() + { + // Create a span from stack-allocated memory + Span span = stackalloc byte[16]; + + Assert.AreEqual(16, span.Length, "Stack-allocated span should have length of 16"); + Assert.IsFalse(span.IsEmpty, "Stack-allocated span should NOT be IsEmpty"); + + // Verify all elements are initialized to zero + for (int i = 0; i < span.Length; i++) + { + Assert.AreEqual((byte)0, span[i], "Stack-allocated span elements should be initialized to 0"); + } + + // Set values in the stack-allocated span + for (int i = 0; i < span.Length; i++) + { + span[i] = (byte)(i * 2); + } + + // Verify the values were set correctly + for (int i = 0; i < span.Length; i++) + { + Assert.AreEqual((byte)(i * 2), span[i], $"Stack-allocated span element at index {i} should be {i * 2}"); + } + } + + [TestMethod] + public void StackAllocSpanWithInitializerTests() + { + // Create a span from stack-allocated memory with initializer + Span span = stackalloc byte[8] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; + + Assert.AreEqual(8, span.Length, "Stack-allocated span with initializer should have length of 8"); + Assert.IsFalse(span.IsEmpty, "Stack-allocated span should NOT be IsEmpty"); + + // Verify the initialized values + for (int i = 0; i < span.Length; i++) + { + Assert.AreEqual((byte)(i + 1), span[i], $"Stack-allocated span element at index {i} should be {i + 1}"); + } + } + + [TestMethod] + public void StackAllocSpanSliceTests() + { + // Create a span from stack-allocated memory + Span span = stackalloc byte[16] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; + + // Slice the stack-allocated span + Span sliced = span.Slice(4, 8); + + Assert.AreEqual(8, sliced.Length, "Sliced stack-allocated span should have length of 8"); + + for (int i = 0; i < sliced.Length; i++) + { + Assert.AreEqual((byte)(i + 4), sliced[i], $"Sliced element at index {i} should be {i + 4}"); + } + + // Modify the sliced span and verify it affects the original + sliced[0] = 0xFF; + Assert.AreEqual((byte)0xFF, span[4], "Modifying sliced span should affect the original stack-allocated span"); + } + + [TestMethod] + public void StackAllocSpanCopyToTests() + { + // Create a span from stack-allocated memory + Span source = stackalloc byte[8] { 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80 }; + + // Create a destination span (also stack-allocated) + Span destination = stackalloc byte[8]; + + // Copy from source to destination + source.CopyTo(destination); + + // Verify the copy + for (int i = 0; i < destination.Length; i++) + { + Assert.AreEqual(source[i], destination[i], $"Copied element at index {i} should match source"); + } + + // Copy to a heap-allocated array + byte[] heapArray = new byte[8]; + Span heapSpan = new Span(heapArray); + source.CopyTo(heapSpan); + + CollectionAssert.AreEqual(source.ToArray(), heapArray, "Stack-allocated span should copy correctly to heap-allocated array"); + } + + [TestMethod] + public void StackAllocSpanToArrayTests() + { + // Create a span from stack-allocated memory + Span span = stackalloc byte[6] { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF }; + + // Convert to array + byte[] array = span.ToArray(); + + Assert.AreEqual(6, array.Length, "ToArray should create an array with the same length"); + + byte[] expected = new byte[] { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF }; + CollectionAssert.AreEqual(expected, array, "Stack-allocated span ToArray should match expected values"); + } + + [TestMethod] + public void StackAllocEmptySpanTests() + { + // Create an empty stack-allocated span + Span span = stackalloc byte[0]; + + Assert.AreEqual(0, span.Length, "Empty stack-allocated span should have length of 0"); + Assert.IsTrue(span.IsEmpty, "Empty stack-allocated span should be IsEmpty"); + } } } diff --git a/nanoFramework.CoreLibrary/System/ReadOnlySpan.cs b/nanoFramework.CoreLibrary/System/ReadOnlySpan.cs index 4d7def6..0b5867b 100644 --- a/nanoFramework.CoreLibrary/System/ReadOnlySpan.cs +++ b/nanoFramework.CoreLibrary/System/ReadOnlySpan.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; @@ -27,10 +28,30 @@ public readonly ref struct ReadOnlySpan /// If the array is , this constructor returns a null . public ReadOnlySpan(T[]? array) { - _array = array ?? Array.Empty(); - _length = _array.Length; + if (array == null) + { + this = default; + return; + } + + NativeReadOnlySpanConstructor( + array, + 0, + array.Length); } + /// + /// Creates a new object from a specified number of T elements starting at a specified memory address. + /// + /// A pointer to the starting address of a specified number of T elements in memory. + /// The number of T elements to be included in the . + /// This constructor should be used with care, since it creates arbitrarily typed Ts from a *-typed block of memory, and neither nor are validated by the constructor. + /// T is a reference type or contains pointers and therefore cannot be stored in unmanaged memory. + /// is negative. + [CLSCompliant(false)] + [MethodImpl(MethodImplOptions.InternalCall)] + public extern unsafe ReadOnlySpan(void* pointer, int length); + /// /// Creates a new that includes a specified number of elements of an array starting at a specified index. /// @@ -65,8 +86,7 @@ public ReadOnlySpan(T[]? array, int start, int length) throw new ArgumentOutOfRangeException(); } - _array = Array.Empty(); - _length = 0; + _array = default; return; } @@ -115,6 +135,72 @@ public ref readonly T this[int index] /// when the current span is empty; otherwise, . public bool IsEmpty => _length == 0; + /// + /// Returns an empty object. + /// + public static ReadOnlySpan Empty => default; + + /// Gets an enumerator for this . + /// An enumerator for this span. + public Enumerator GetEnumerator() => new Enumerator(this); + + /// + /// Provides an enumerator for the elements of a . + /// + public ref struct Enumerator : IEnumerator + { + /// The span being enumerated. + private readonly ReadOnlySpan _span; + /// The next index to yield. + private int _index; + + /// Initialize the enumerator. + /// The span to enumerate. + internal Enumerator(ReadOnlySpan span) + { + _span = span; + _index = -1; + } + + /// Advances the enumerator to the next item of the . + /// + /// if the enumerator successfully advanced to the next item; if the end of the span has been passed. + /// + public bool MoveNext() + { + int index = _index + 1; + + if (index < _span.Length) + { + _index = index; + return true; + } + + return false; + } + + /// Gets the element at the current position of the enumerator. + public ref readonly T Current + { + get + { + return ref _span[_index]; + } + } + + /// + T IEnumerator.Current => Current; + + /// + object Collections.IEnumerator.Current => Current!; + + /// + void Collections.IEnumerator.Reset() => _index = -1; + + /// + void IDisposable.Dispose() { } + } + /// /// Returns a value that indicates whether two instances are not equal. /// @@ -162,6 +248,44 @@ public ref readonly T this[int index] return true; } + /// + /// Forms a slice out of the current span that begins at a specified index. + /// + /// The zero-based index at which to begin the slice. + /// A span that consists of all elements of the current span from to the end of the span. + /// + /// start is greater than the number of items in the read-only span. + /// + public ReadOnlySpan Slice(int start) + { + if ((uint)start > (uint)_length) + { + throw new ArgumentOutOfRangeException(); + } + + return new ReadOnlySpan(_array, start, _length - start); + } + + /// + /// Forms a slice out of the current read-only span starting at a specified index for a specified length. + /// + /// The zero-based index at which to begin the slice. + /// The desired length for the slice. + /// A span that consists of elements from the current span starting at . + /// + /// is less than zero or greater than . + /// + public ReadOnlySpan Slice(int start, int length) + { + if ((uint)start > (uint)_length + || (uint)length > (uint)(_length - start)) + { + throw new ArgumentOutOfRangeException(); + } + + return new ReadOnlySpan(_array, start, length); + } + /// /// Copies the contents of this read-only span into a new array. /// diff --git a/nanoFramework.CoreLibrary/System/Span.cs b/nanoFramework.CoreLibrary/System/Span.cs index 2a2670e..9210b2f 100644 --- a/nanoFramework.CoreLibrary/System/Span.cs +++ b/nanoFramework.CoreLibrary/System/Span.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; @@ -21,11 +22,9 @@ public readonly ref struct Span private readonly T[] _array; private readonly int _length; - /// /// /// Creates a new object over the entirety of a specified array. /// - /// /// The array from which to create the object. /// If array is , this constructor returns a . /// is a reference type, and is not an array of type . @@ -42,10 +41,24 @@ public Span(T[]? array) throw new ArrayTypeMismatchException(); } - _array = array ?? []; - _length = _array.Length; + NativeSpanConstructor( + array, + 0, + array.Length); } + /// + /// Creates a new object from a specified number of T elements starting at a specified memory address. + /// + /// A pointer to the starting address of a specified number of T elements in memory. + /// The number of T elements to be included in the . + /// This constructor should be used with care, since it creates arbitrarily typed Ts from a *-typed block of memory, and neither nor are validated by the constructor. + /// T is a reference type or contains pointers and therefore cannot be stored in unmanaged memory. + /// is negative. + [CLSCompliant(false)] + [MethodImpl(MethodImplOptions.InternalCall)] + public extern unsafe Span(void* pointer, int length); + /// /// Creates a new object that includes a specified number of elements of an array starting at a specified index. /// @@ -163,7 +176,7 @@ public ref T this[int index] /// /// Provides an enumerator for the elements of a . /// - public ref struct Enumerator + public ref struct Enumerator : IEnumerator { /// The span being enumerated. private readonly Span _span; @@ -197,6 +210,18 @@ public bool MoveNext() /// Gets a reference to the item at the current position of the enumerator. public ref T Current => ref _span[_index]; + + /// + T IEnumerator.Current => Current; + + /// + object Collections.IEnumerator.Current => Current!; + + /// + void Collections.IEnumerator.Reset() => _index = -1; + + /// + void IDisposable.Dispose() { } } /// @@ -257,7 +282,7 @@ public static implicit operator ReadOnlySpan(Span span) => /// The zero-based index at which to begin the slice. /// A span that consists of all elements of the current span from to the end of the span. /// - /// is less than zero or greater than . + /// is less than zero or greater than the number of elements in the current span. /// public Span Slice(int start) { @@ -317,4 +342,4 @@ private extern void NativeSpanConstructor( } } -#pragma warning restore S3928 // Parameter names used into ArgumentException constructors should match an existing one +#pragma warning restore S3928 // Parameter names used into ArgumentException constructors should match an existing one