// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; #if NET6_0_OR_GREATER using System.Buffers; #endif using System.Runtime.CompilerServices; using CommunityToolkit.HighPerformance.Enumerables; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace CommunityToolkit.HighPerformance.UnitTests; /* ==================================================================== * NOTE * ==================================================================== * All the tests here mirror the ones for ReadOnlySpan2D. See comments * in the test file for Span2D for more info on these tests. */ [TestClass] public class Test_ReadOnlySpan2DT { [TestMethod] public void Test_ReadOnlySpan2DT_Empty() { ReadOnlySpan2D empty1 = default; Assert.IsTrue(empty1.IsEmpty); Assert.AreEqual(0, empty1.Length); Assert.AreEqual(0, empty1.Width); Assert.AreEqual(0, empty1.Height); ReadOnlySpan2D empty2 = ReadOnlySpan2D.Empty; Assert.IsTrue(empty2.IsEmpty); Assert.AreEqual(0, empty2.Length); Assert.AreEqual(0, empty2.Width); Assert.AreEqual(0, empty2.Height); ReadOnlySpan2D empty3 = new([], 0, 0); Assert.IsTrue(empty3.IsEmpty); Assert.AreEqual(0, empty3.Length); Assert.AreEqual(0, empty3.Width); Assert.AreEqual(0, empty3.Height); ReadOnlySpan2D empty4 = new([], 4, 0); Assert.IsTrue(empty4.IsEmpty); Assert.AreEqual(0, empty4.Length); Assert.AreEqual(0, empty4.Width); Assert.AreEqual(4, empty4.Height); ReadOnlySpan2D empty5 = new([], 0, 7); Assert.IsTrue(empty5.IsEmpty); Assert.AreEqual(0, empty5.Length); Assert.AreEqual(7, empty5.Width); Assert.AreEqual(0, empty5.Height); } #if NET6_0_OR_GREATER [TestMethod] public unsafe void Test_ReadOnlySpan2DT_RefConstructor() { ReadOnlySpan span = stackalloc[] { 1, 2, 3, 4, 5, 6 }; ReadOnlySpan2D span2d = ReadOnlySpan2D.DangerousCreate(span[0], 2, 3, 0); Assert.IsFalse(span2d.IsEmpty); Assert.AreEqual(6, span2d.Length); Assert.AreEqual(3, span2d.Width); Assert.AreEqual(2, span2d.Height); Assert.AreEqual(1, span2d[0, 0]); Assert.AreEqual(6, span2d[1, 2]); _ = Assert.ThrowsExactly(() => ReadOnlySpan2D.DangerousCreate(Unsafe.AsRef(null), -1, 0, 0)); _ = Assert.ThrowsExactly(() => ReadOnlySpan2D.DangerousCreate(Unsafe.AsRef(null), 1, -2, 0)); _ = Assert.ThrowsExactly(() => ReadOnlySpan2D.DangerousCreate(Unsafe.AsRef(null), 1, 0, -5)); } #endif [TestMethod] public unsafe void Test_ReadOnlySpan2DT_PtrConstructor() { int* ptr = stackalloc[] { 1, 2, 3, 4, 5, 6 }; ReadOnlySpan2D span2d = new(ptr, 2, 3, 0); Assert.IsFalse(span2d.IsEmpty); Assert.AreEqual(6, span2d.Length); Assert.AreEqual(3, span2d.Width); Assert.AreEqual(2, span2d.Height); Assert.AreEqual(1, span2d[0, 0]); Assert.AreEqual(6, span2d[1, 2]); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D((void*)0, -1, 0, 0)); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D((void*)0, 1, -2, 0)); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D((void*)0, 1, 0, -5)); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D((void*)0, 2, 2, 0)); } [TestMethod] public void Test_ReadOnlySpan2DT_Array1DConstructor() { int[] array = { 1, 2, 3, 4, 5, 6 }; ReadOnlySpan2D span2d = new(array, 1, 2, 2, 1); Assert.IsFalse(span2d.IsEmpty); Assert.AreEqual(4, span2d.Length); Assert.AreEqual(2, span2d.Width); Assert.AreEqual(2, span2d.Height); Assert.AreEqual(2, span2d[0, 0]); Assert.AreEqual(6, span2d[1, 1]); // Same for ReadOnlyMemory2D, we need to check that covariant array conversions are allowed _ = new ReadOnlySpan2D(new string[1], 1, 1); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array, -99, 1, 1, 1)); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array, 0, -10, 1, 1)); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array, 0, 1, 1, -1)); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array, 0, 1, -100, 1)); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array, 0, 10, 1, 120)); } [TestMethod] public void Test_ReadOnlySpan2DT_Array2DConstructor_1() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; ReadOnlySpan2D span2d = new(array); Assert.IsFalse(span2d.IsEmpty); Assert.AreEqual(6, span2d.Length); Assert.AreEqual(3, span2d.Width); Assert.AreEqual(2, span2d.Height); Assert.AreEqual(2, span2d[0, 1]); Assert.AreEqual(6, span2d[1, 2]); _ = new ReadOnlySpan2D(new string[1, 2]); } [TestMethod] public void Test_ReadOnlySpan2DT_Array2DConstructor_2() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; ReadOnlySpan2D span2d = new(array, 0, 1, 2, 2); Assert.IsFalse(span2d.IsEmpty); Assert.AreEqual(4, span2d.Length); Assert.AreEqual(2, span2d.Width); Assert.AreEqual(2, span2d.Height); Assert.AreEqual(2, span2d[0, 0]); Assert.AreEqual(6, span2d[1, 1]); _ = new ReadOnlySpan2D(new string[1, 2]); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(new string[1, 2], 0, 0, 2, 2)); } [TestMethod] public void Test_ReadOnlySpan2DT_Array3DConstructor_1() { int[,,] array = { { { 1, 2, 3 }, { 4, 5, 6 } }, { { 10, 20, 30 }, { 40, 50, 60 } } }; ReadOnlySpan2D span2d = new(array, 1); Assert.IsFalse(span2d.IsEmpty); Assert.AreEqual(6, span2d.Length); Assert.AreEqual(3, span2d.Width); Assert.AreEqual(2, span2d.Height); Assert.AreEqual(10, span2d[0, 0]); Assert.AreEqual(20, span2d[0, 1]); Assert.AreEqual(60, span2d[1, 2]); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array, -1)); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array, 20)); } [TestMethod] public void Test_ReadOnlySpan2DT_Array3DConstructor_2() { int[,,] array = { { { 1, 2, 3 }, { 4, 5, 6 } }, { { 10, 20, 30 }, { 40, 50, 60 } } }; ReadOnlySpan2D span2d = new(array, 1, 0, 1, 2, 2); Assert.IsFalse(span2d.IsEmpty); Assert.AreEqual(4, span2d.Length); Assert.AreEqual(2, span2d.Width); Assert.AreEqual(2, span2d.Height); Assert.AreEqual(20, span2d[0, 0]); Assert.AreEqual(30, span2d[0, 1]); Assert.AreEqual(60, span2d[1, 1]); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array, -1, 1, 1, 1, 1)); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array, 1, -1, 1, 1, 1)); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array, 1, 1, -1, 1, 1)); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array, 1, 1, 1, -1, 1)); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array, 1, 1, 1, 1, -1)); } [TestMethod] public void Test_ReadOnlySpan2DT_CopyTo_1() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; ReadOnlySpan2D span2d = new(array); int[] target = new int[array.Length]; span2d.CopyTo(target); CollectionAssert.AreEqual(array, target); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array).CopyTo(Span.Empty)); } [TestMethod] public void Test_ReadOnlySpan2DT_CopyTo_2() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; ReadOnlySpan2D span2d = new(array, 0, 1, 2, 2); int[] target = new int[4]; span2d.CopyTo(target); int[] expected = { 2, 3, 5, 6 }; CollectionAssert.AreEqual(target, expected); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array).CopyTo(Span.Empty)); } [TestMethod] public void Test_ReadOnlySpan2DT_CopyTo2D_1() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; ReadOnlySpan2D span2d = new(array); int[,] target = new int[2, 3]; span2d.CopyTo(target); CollectionAssert.AreEqual(array, target); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array).CopyTo(Span2D.Empty)); } [TestMethod] public void Test_ReadOnlySpan2DT_CopyTo2D_2() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; ReadOnlySpan2D span2d = new(array, 0, 1, 2, 2); int[,] target = new int[2, 2]; span2d.CopyTo(target); int[,] expected = { { 2, 3 }, { 5, 6 } }; CollectionAssert.AreEqual(target, expected); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array).CopyTo(new Span2D(target))); } [TestMethod] public void Test_ReadOnlySpan2DT_TryCopyTo() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; ReadOnlySpan2D span2d = new(array); int[] target = new int[array.Length]; Assert.IsTrue(span2d.TryCopyTo(target)); Assert.IsFalse(span2d.TryCopyTo(Span.Empty)); } [TestMethod] public void Test_ReadOnlySpan2DT_TryCopyTo2D() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; ReadOnlySpan2D span2d = new(array); int[,] target = new int[2, 3]; Assert.IsTrue(span2d.TryCopyTo(target)); Assert.IsFalse(span2d.TryCopyTo(Span2D.Empty)); } [TestMethod] public unsafe void Test_ReadOnlySpan2DT_GetPinnableReference() { Assert.IsTrue(Unsafe.AreSame( ref Unsafe.AsRef(null), ref Unsafe.AsRef(in ReadOnlySpan2D.Empty.GetPinnableReference()))); int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; ReadOnlySpan2D span2d = new(array); ref int r0 = ref Unsafe.AsRef(in span2d.GetPinnableReference()); Assert.IsTrue(Unsafe.AreSame(ref r0, ref array[0, 0])); } [TestMethod] public unsafe void Test_ReadOnlySpan2DT_DangerousGetReference() { Assert.IsTrue(Unsafe.AreSame( ref Unsafe.AsRef(null), ref ReadOnlySpan2D.Empty.DangerousGetReference())); int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; ReadOnlySpan2D span2d = new(array); ref int r0 = ref span2d.DangerousGetReference(); Assert.IsTrue(Unsafe.AreSame(ref r0, ref array[0, 0])); } #if NET6_0_OR_GREATER [TestMethod] public unsafe void Test_ReadOnlySpan2DT_Index_Indexer_1() { int[,] array = new int[4, 4]; ReadOnlySpan2D span2d = new(array); ref int arrayRef = ref array[1, 3]; ref readonly int span2dRef = ref span2d[1, ^1]; Assert.IsTrue(Unsafe.AreSame(ref arrayRef, ref Unsafe.AsRef(in span2dRef))); } [TestMethod] public unsafe void Test_ReadOnlySpan2DT_Index_Indexer_2() { int[,] array = new int[4, 4]; ReadOnlySpan2D span2d = new(array); ref int arrayRef = ref array[2, 1]; ref readonly int span2dRef = ref span2d[^2, ^3]; Assert.IsTrue(Unsafe.AreSame(ref arrayRef, ref Unsafe.AsRef(in span2dRef))); } [TestMethod] public unsafe void Test_ReadOnlySpan2DT_Index_Indexer_Fail() { int[,] array = new int[4, 4]; _ = Assert.ThrowsExactly(() => { ReadOnlySpan2D span2d = new(array); ref readonly int span2dRef = ref span2d[^6, 2]; }); } [TestMethod] public unsafe void Test_ReadOnlySpan2DT_Range_Indexer_1() { int[,] array = new int[4, 4]; ReadOnlySpan2D span2d = new(array); ReadOnlySpan2D slice = span2d[1.., 1..]; Assert.AreEqual(9, slice.Length); Assert.IsTrue(Unsafe.AreSame(ref array[1, 1], ref Unsafe.AsRef(in slice[0, 0]))); Assert.IsTrue(Unsafe.AreSame(ref array[3, 3], ref Unsafe.AsRef(in slice[2, 2]))); } [TestMethod] public unsafe void Test_ReadOnlySpan2DT_Range_Indexer_2() { int[,] array = new int[4, 4]; ReadOnlySpan2D span2d = new(array); ReadOnlySpan2D slice = span2d[0..^2, 1..^1]; Assert.AreEqual(4, slice.Length); Assert.IsTrue(Unsafe.AreSame(ref array[0, 1], ref Unsafe.AsRef(in slice[0, 0]))); Assert.IsTrue(Unsafe.AreSame(ref array[1, 2], ref Unsafe.AsRef(in slice[1, 1]))); } [TestMethod] public unsafe void Test_ReadOnlySpan2DT_Range_Indexer_Fail() { int[,] array = new int[4, 4]; _ = Assert.ThrowsExactly(() => { ReadOnlySpan2D span2d = new(array); _ = span2d[0..6, 2..^1]; }); } #endif [TestMethod] public void Test_ReadOnlySpan2DT_Slice_1() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; ReadOnlySpan2D span2d = new(array); ReadOnlySpan2D slice1 = span2d.Slice(1, 1, 1, 2); Assert.AreEqual(2, slice1.Length); Assert.AreEqual(1, slice1.Height); Assert.AreEqual(2, slice1.Width); Assert.AreEqual(5, slice1[0, 0]); Assert.AreEqual(6, slice1[0, 1]); ReadOnlySpan2D slice2 = span2d.Slice(0, 1, 2, 2); Assert.AreEqual(4, slice2.Length); Assert.AreEqual(2, slice2.Height); Assert.AreEqual(2, slice2.Width); Assert.AreEqual(2, slice2[0, 0]); Assert.AreEqual(5, slice2[1, 0]); Assert.AreEqual(6, slice2[1, 1]); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array).Slice(-1, 1, 1, 1)); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array).Slice(1, -1, 1, 1)); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array).Slice(1, 1, 1, -1)); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array).Slice(1, 1, -1, 1)); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array).Slice(10, 1, 1, 1)); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array).Slice(1, 12, 1, 12)); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array).Slice(1, 1, 55, 1)); } [TestMethod] public void Test_ReadOnlySpan2DT_Slice_2() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; ReadOnlySpan2D span2d = new(array); ReadOnlySpan2D slice1 = span2d.Slice(0, 0, 2, 2); Assert.AreEqual(4, slice1.Length); Assert.AreEqual(2, slice1.Height); Assert.AreEqual(2, slice1.Width); Assert.AreEqual(1, slice1[0, 0]); Assert.AreEqual(5, slice1[1, 1]); ReadOnlySpan2D slice2 = slice1.Slice(1, 0, 1, 2); Assert.AreEqual(2, slice2.Length); Assert.AreEqual(1, slice2.Height); Assert.AreEqual(2, slice2.Width); Assert.AreEqual(4, slice2[0, 0]); Assert.AreEqual(5, slice2[0, 1]); ReadOnlySpan2D slice3 = slice2.Slice(0, 1, 1, 1); Assert.AreEqual(1, slice3.Length); Assert.AreEqual(1, slice3.Height); Assert.AreEqual(1, slice3.Width); Assert.AreEqual(5, slice3[0, 0]); } #if NET6_0_OR_GREATER [TestMethod] public void Test_ReadOnlySpan2DT_GetRowReadOnlySpan() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; ReadOnlySpan2D span2d = new(array); ReadOnlySpan span = span2d.GetRowSpan(1); Assert.IsTrue(Unsafe.AreSame( ref Unsafe.AsRef(in span[0]), ref array[1, 0])); Assert.IsTrue(Unsafe.AreSame( ref Unsafe.AsRef(in span[2]), ref array[1, 2])); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array).GetRowSpan(-1)); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array).GetRowSpan(5)); } #endif [TestMethod] public void Test_ReadOnlySpan2DT_TryGetSpan_From1DArray_1() { int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; ReadOnlySpan2D span2d = new(array, 3, 3); bool success = span2d.TryGetSpan(out ReadOnlySpan span); Assert.IsTrue(success); Assert.AreEqual(span.Length, span2d.Length); Assert.IsTrue(Unsafe.AreSame(ref array[0], ref Unsafe.AsRef(in span[0]))); } [TestMethod] public void Test_ReadOnlySpan2DT_TryGetSpan_From1DArray_2() { int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; ReadOnlySpan2D span2d = new ReadOnlySpan2D(array, 3, 3).Slice(1, 0, 2, 3); bool success = span2d.TryGetSpan(out ReadOnlySpan span); Assert.IsTrue(success); Assert.AreEqual(span.Length, span2d.Length); Assert.IsTrue(Unsafe.AreSame(ref array[3], ref Unsafe.AsRef(in span[0]))); } [TestMethod] public void Test_ReadOnlySpan2DT_TryGetSpan_From1DArray_3() { int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; ReadOnlySpan2D span2d = new ReadOnlySpan2D(array, 3, 3).Slice(0, 1, 3, 2); bool success = span2d.TryGetSpan(out ReadOnlySpan span); Assert.IsFalse(success); Assert.AreEqual(0, span.Length); } [TestMethod] public void Test_ReadOnlySpan2DT_TryGetReadOnlySpan_From2DArray_1() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; ReadOnlySpan2D span2d = new(array); bool success = span2d.TryGetSpan(out ReadOnlySpan span); #if NETFRAMEWORK // Can't get a ReadOnlySpan over a T[,] array on .NET Standard 2.0 Assert.IsFalse(success); Assert.AreEqual(0, span.Length); #else Assert.IsTrue(success); Assert.AreEqual(span.Length, span2d.Length); #endif } [TestMethod] public void Test_ReadOnlySpan2DT_TryGetReadOnlySpan_From2DArray_2() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; ReadOnlySpan2D span2d = new(array, 0, 0, 2, 2); bool success = span2d.TryGetSpan(out ReadOnlySpan span); Assert.IsFalse(success); Assert.IsTrue(span.IsEmpty); } [TestMethod] public void Test_ReadOnlySpan2DT_ToArray_1() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; ReadOnlySpan2D span2d = new(array); int[,] copy = span2d.ToArray(); Assert.AreEqual(copy.GetLength(0), array.GetLength(0)); Assert.AreEqual(copy.GetLength(1), array.GetLength(1)); CollectionAssert.AreEqual(array, copy); } [TestMethod] public void Test_ReadOnlySpan2DT_ToArray_2() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; ReadOnlySpan2D span2d = new(array, 0, 0, 2, 2); int[,] copy = span2d.ToArray(); Assert.AreEqual(2, copy.GetLength(0)); Assert.AreEqual(2, copy.GetLength(1)); int[,] expected = { { 1, 2 }, { 4, 5 } }; CollectionAssert.AreEqual(expected, copy); } [TestMethod] public void Test_ReadOnlySpan2DT_Equals() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; _ = Assert.ThrowsExactly(() => { ReadOnlySpan2D span2d = new(array); _ = span2d.Equals(null); }); } [TestMethod] public void Test_ReadOnlySpan2DT_GetHashCode() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; _ = Assert.ThrowsExactly(() => { ReadOnlySpan2D span2d = new(array); _ = span2d.GetHashCode(); }); } [TestMethod] public void Test_ReadOnlySpan2DT_ToString() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; ReadOnlySpan2D span2d = new(array); string text = span2d.ToString(); const string expected = "CommunityToolkit.HighPerformance.ReadOnlySpan2D[2, 3]"; Assert.AreEqual(expected, text); } [TestMethod] public void Test_ReadOnlySpan2DT_opEquals() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; ReadOnlySpan2D span2d_1 = new(array); ReadOnlySpan2D span2d_2 = new(array); Assert.IsTrue(span2d_1 == span2d_2); Assert.IsFalse(span2d_1 == ReadOnlySpan2D.Empty); Assert.IsTrue(ReadOnlySpan2D.Empty == ReadOnlySpan2D.Empty); ReadOnlySpan2D span2d_3 = new(array, 0, 0, 2, 2); Assert.IsFalse(span2d_1 == span2d_3); Assert.IsFalse(span2d_3 == ReadOnlySpan2D.Empty); } [TestMethod] public void Test_ReadOnlySpan2DT_ImplicitCast() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; ReadOnlySpan2D span2d_1 = array; ReadOnlySpan2D span2d_2 = new(array); Assert.IsTrue(span2d_1 == span2d_2); } [TestMethod] public void Test_ReadOnlySpan2DT_GetRow() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; int i = 0; foreach (ref readonly int value in new ReadOnlySpan2D(array).GetRow(1)) { Assert.IsTrue(Unsafe.AreSame(ref Unsafe.AsRef(in value), ref array[1, i++])); } ReadOnlyRefEnumerable enumerable = new ReadOnlySpan2D(array).GetRow(1); int[] expected = { 4, 5, 6 }; CollectionAssert.AreEqual(enumerable.ToArray(), expected); Assert.AreSame(default(ReadOnlyRefEnumerable).ToArray(), Array.Empty()); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array).GetRow(-1)); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array).GetRow(2)); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array).GetRow(1000)); } [TestMethod] public unsafe void Test_ReadOnlySpan2DT_Pointer_GetRow() { int* array = stackalloc[] { 1, 2, 3, 4, 5, 6 }; int i = 0; foreach (ref readonly int value in new ReadOnlySpan2D(array, 2, 3, 0).GetRow(1)) { Assert.IsTrue(Unsafe.AreSame(ref Unsafe.AsRef(in value), ref array[3 + i++])); } ReadOnlyRefEnumerable enumerable = new ReadOnlySpan2D(array, 2, 3, 0).GetRow(1); int[] expected = { 4, 5, 6 }; CollectionAssert.AreEqual(enumerable.ToArray(), expected); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array, 2, 3, 0).GetRow(-1)); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array, 2, 3, 0).GetRow(2)); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array, 2, 3, 0).GetRow(1000)); } [TestMethod] public void Test_ReadOnlySpan2DT_GetColumn() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; int i = 0; foreach (ref readonly int value in new ReadOnlySpan2D(array).GetColumn(1)) { Assert.IsTrue(Unsafe.AreSame(ref Unsafe.AsRef(in value), ref array[i++, 1])); } ReadOnlyRefEnumerable enumerable = new ReadOnlySpan2D(array).GetColumn(2); int[] expected = { 3, 6 }; CollectionAssert.AreEqual(enumerable.ToArray(), expected); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array).GetColumn(-1)); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array).GetColumn(3)); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array).GetColumn(1000)); } [TestMethod] public unsafe void Test_ReadOnlySpan2DT_Pointer_GetColumn() { int* array = stackalloc[] { 1, 2, 3, 4, 5, 6 }; int i = 0; foreach (ref readonly int value in new ReadOnlySpan2D(array, 2, 3, 0).GetColumn(1)) { Assert.IsTrue(Unsafe.AreSame(ref Unsafe.AsRef(in value), ref array[(i++ * 3) + 1])); } ReadOnlyRefEnumerable enumerable = new ReadOnlySpan2D(array, 2, 3, 0).GetColumn(2); int[] expected = { 3, 6 }; CollectionAssert.AreEqual(enumerable.ToArray(), expected); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array, 2, 3, 0).GetColumn(-1)); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array, 2, 3, 0).GetColumn(3)); _ = Assert.ThrowsExactly(() => new ReadOnlySpan2D(array, 2, 3, 0).GetColumn(1000)); } [TestMethod] public void Test_ReadOnlySpan2DT_GetEnumerator() { int[,] array = { { 1, 2, 3 }, { 4, 5, 6 } }; int[] result = new int[4]; int i = 0; foreach (int item in new ReadOnlySpan2D(array, 0, 1, 2, 2)) { result[i++] = item; } int[] expected = { 2, 3, 5, 6 }; CollectionAssert.AreEqual(result, expected); } [TestMethod] public unsafe void Test_ReadOnlySpan2DT_Pointer_GetEnumerator() { int* array = stackalloc[] { 1, 2, 3, 4, 5, 6 }; int[] result = new int[4]; int i = 0; foreach (int item in new ReadOnlySpan2D(array + 1, 2, 2, 1)) { result[i++] = item; } int[] expected = { 2, 3, 5, 6 }; CollectionAssert.AreEqual(result, expected); } [TestMethod] public void Test_ReadOnlySpan2DT_GetEnumerator_Empty() { ReadOnlySpan2D.Enumerator enumerator = ReadOnlySpan2D.Empty.GetEnumerator(); Assert.IsFalse(enumerator.MoveNext()); } [TestMethod] public void Test_ReadOnlySpan2DT_ReadOnlyRefEnumerable_Misc() { int[,] array1 = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 }, { 13, 14, 15, 16 } }; ReadOnlySpan2D span1 = array1; int[,] array2 = new int[4, 4]; // Copy to enumerable with source step == 1, destination step == 1 span1.GetRow(0).CopyTo(array2.GetRow(0)); // Copy enumerable with source step == 1, destination step != 1 span1.GetRow(1).CopyTo(array2.GetColumn(1)); // Copy enumerable with source step != 1, destination step == 1 span1.GetColumn(2).CopyTo(array2.GetRow(2)); // Copy enumerable with source step != 1, destination step != 1 span1.GetColumn(3).CopyTo(array2.GetColumn(3)); int[,] result = { { 1, 5, 3, 4 }, { 0, 6, 0, 8 }, { 3, 7, 11, 12 }, { 0, 8, 0, 16 } }; CollectionAssert.AreEqual(array2, result); // Test a valid and an invalid TryCopyTo call with the RefEnumerable overload bool shouldBeTrue = span1.GetRow(0).TryCopyTo(array2.GetColumn(0)); bool shouldBeFalse = span1.GetRow(0).TryCopyTo(default(RefEnumerable)); result = new[,] { { 1, 5, 3, 4 }, { 2, 6, 0, 8 }, { 3, 7, 11, 12 }, { 4, 8, 0, 16 } }; CollectionAssert.AreEqual(array2, result); Assert.IsTrue(shouldBeTrue); Assert.IsFalse(shouldBeFalse); } [TestMethod] public void Test_ReadOnlySpan2DT_ReadOnlyRefEnumerable_Cast() { int[,] array1 = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 }, { 13, 14, 15, 16 } }; int[] result = { 5, 6, 7, 8 }; // Cast a RefEnumerable to a readonly one and verify the contents int[] row = ((ReadOnlyRefEnumerable)array1.GetRow(1)).ToArray(); CollectionAssert.AreEqual(result, row); } #if NET6_0_OR_GREATER [TestMethod] public void Test_ReadOnlySpan2DT_FromMemoryManager_Indexing() { const int w = 10; const int h = 10; const int l = w * h; byte[] b = new byte[l]; short[] s = new short[l]; for (int i = 0; i < l; ++i) { b[i] = (byte)i; s[i] = (short)i; } Memory2DTester byteTester = new(w, h, b); Span2D byteSpan2DFromArray = byteTester.GetMemory2DFromArray().Span; Assert.AreEqual(11, byteSpan2DFromArray[0, 0]); Span2D byteSpan2DFromMemoryManager = byteTester.GetMemory2DFromMemoryManager().Span; Assert.AreEqual(11, byteSpan2DFromMemoryManager[0, 0]); Memory2DTester shortTester = new(w, h, s); Span2D shortSpan2DFromArray = shortTester.GetMemory2DFromArray().Span; Span2D shortSpan2DFromMemoryManager = shortTester.GetMemory2DFromMemoryManager().Span; Assert.AreEqual(11, shortSpan2DFromArray[0, 0]); Assert.AreEqual(11, shortSpan2DFromMemoryManager[0, 0]); } #endif } #if NET6_0_OR_GREATER public sealed class Memory2DTester : MemoryManager where T : unmanaged { private readonly T[] data; public Memory2DTester(int w, int h, T[] data) { if (w < 2 || h < 2) { throw new ArgumentException("The 'w' and 'h' arguments must be at least 2."); } this.data = data; Width = w; Height = h; } public int Width { get; } public int Height { get; } public Memory2D GetMemory2DFromMemoryManager() { return new(this, Width + 1, Height - 1, Width - 1, 1); } public Memory2D GetMemory2DFromArray() { return new(this.data, Width + 1, Height - 1, Width - 1, 1); } /// public override Span GetSpan() { return new(this.data); } /// public override MemoryHandle Pin(int elementIndex = 0) { return default; } /// public override void Unpin() { } /// protected override void Dispose(bool disposing) { } } #endif