// 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; using System.Runtime.CompilerServices; #if NETSTANDARD2_1_OR_GREATER using System.Runtime.InteropServices; using CommunityToolkit.HighPerformance.Buffers.Internals; #endif using CommunityToolkit.HighPerformance.Enumerables; using CommunityToolkit.HighPerformance.Helpers; using CommunityToolkit.HighPerformance.Helpers.Internals; using RuntimeHelpers = CommunityToolkit.HighPerformance.Helpers.Internals.RuntimeHelpers; namespace CommunityToolkit.HighPerformance; /// partial class ArrayExtensions { /// /// Returns a reference to the first element within a given 2D array, with no bounds checks. /// /// The type of elements in the input 2D array instance. /// The input array instance. /// A reference to the first element within , or the location it would have used, if is empty. /// This method doesn't do any bounds checks, therefore it is responsibility of the caller to perform checks in case the returned value is dereferenced. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ref T DangerousGetReference(this T[,] array) { #if NET6_0_OR_GREATER return ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(array)); #elif NETCOREAPP3_1 RawArray2DData? arrayData = Unsafe.As(array)!; ref T r0 = ref Unsafe.As(ref arrayData.Data); return ref r0; #else IntPtr offset = RuntimeHelpers.GetArray2DDataByteOffset(); return ref ObjectMarshal.DangerousGetObjectDataReferenceAt(array, offset); #endif } /// /// Returns a reference to an element at a specified coordinate within a given 2D array, with no bounds checks. /// /// The type of elements in the input 2D array instance. /// The input 2D array instance. /// The vertical index of the element to retrieve within . /// The horizontal index of the element to retrieve within . /// A reference to the element within at the coordinate specified by and . /// /// This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the /// and parameters are valid. Furthermore, this extension will ignore the lower bounds for the input /// array, and will just assume that the input index is 0-based. It is responsibility of the caller to adjust the input /// indices to account for the actual lower bounds, if the input array has either axis not starting at 0. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ref T DangerousGetReferenceAt(this T[,] array, int i, int j) { #if NET6_0_OR_GREATER int width = array.GetLength(1); nint index = ((nint)(uint)i * (nint)(uint)width) + (nint)(uint)j; ref T r0 = ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(array)); ref T ri = ref Unsafe.Add(ref r0, index); return ref ri; #elif NETCOREAPP3_1 RawArray2DData? arrayData = Unsafe.As(array)!; nint offset = ((nint)(uint)i * (nint)(uint)arrayData.Width) + (nint)(uint)j; ref T r0 = ref Unsafe.As(ref arrayData.Data); ref T ri = ref Unsafe.Add(ref r0, offset); return ref ri; #else int width = array.GetLength(1); nint index = ((nint)(uint)i * (nint)(uint)width) + (nint)(uint)j; IntPtr offset = RuntimeHelpers.GetArray2DDataByteOffset(); ref T r0 = ref ObjectMarshal.DangerousGetObjectDataReferenceAt(array, offset); ref T ri = ref Unsafe.Add(ref r0, index); return ref ri; #endif } #if NETCOREAPP3_1 // Description adapted from CoreCLR: see https://source.dot.net/#System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs,285. // CLR 2D arrays are laid out in memory as follows: // [ sync block || pMethodTable || Length (padded to IntPtr) || HxW || HxW bounds || array data .. ] // ^ ^ // | \-- ref Unsafe.As(array).Data // \-- array // The length is always padded to IntPtr just like with SZ arrays. // The total data padding is therefore 20 bytes on x86 (4 + 4 + 4 + 4 + 4), or 24 bytes on x64. [StructLayout(LayoutKind.Sequential)] private sealed class RawArray2DData { #pragma warning disable CS0649 // Unassigned fields public IntPtr Length; public int Height; public int Width; public int HeightLowerBound; public int WidthLowerBound; public byte Data; #pragma warning restore CS0649 } #endif /// /// Returns a over a row in a given 2D array instance. /// /// The type of elements in the input 2D array instance. /// The input array instance. /// The target row to retrieve (0-based index). /// A with the items from the target row within . /// The returned value shouldn't be used directly: use this extension in a loop. /// Thrown when one of the input parameters is out of range. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static RefEnumerable GetRow(this T[,] array, int row) { if (array.IsCovariant()) { ThrowArrayTypeMismatchException(); } int height = array.GetLength(0); if ((uint)row >= (uint)height) { ThrowArgumentOutOfRangeExceptionForRow(); } int width = array.GetLength(1); #if NETSTANDARD2_1_OR_GREATER ref T r0 = ref array.DangerousGetReferenceAt(row, 0); return new(ref r0, width, 1); #else ref T r0 = ref array.DangerousGetReferenceAt(row, 0); IntPtr offset = ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref r0); return new(array, offset, width, 1); #endif } /// /// Returns a that returns the items from a given column in a given 2D array instance. /// This extension should be used directly within a loop: /// /// int[,] matrix = /// { /// { 1, 2, 3 }, /// { 4, 5, 6 }, /// { 7, 8, 9 } /// }; /// /// foreach (ref int number in matrix.GetColumn(1)) /// { /// // Access the current number by reference here... /// } /// /// The compiler will take care of properly setting up the loop with the type returned from this method. /// /// The type of elements in the input 2D array instance. /// The input array instance. /// The target column to retrieve (0-based index). /// A wrapper type that will handle the column enumeration for . /// The returned value shouldn't be used directly: use this extension in a loop. /// Thrown when one of the input parameters is out of range. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static RefEnumerable GetColumn(this T[,] array, int column) { if (array.IsCovariant()) { ThrowArrayTypeMismatchException(); } int width = array.GetLength(1); if ((uint)column >= (uint)width) { ThrowArgumentOutOfRangeExceptionForColumn(); } int height = array.GetLength(0); #if NETSTANDARD2_1_OR_GREATER ref T r0 = ref array.DangerousGetReferenceAt(0, column); return new(ref r0, height, width); #else ref T r0 = ref array.DangerousGetReferenceAt(0, column); IntPtr offset = ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref r0); return new(array, offset, height, width); #endif } /// /// Creates a new over an input 2D array. /// /// The type of elements in the input 2D array instance. /// The input 2D array instance. /// A instance with the values of . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span2D AsSpan2D(this T[,]? array) { return new(array); } /// /// Creates a new over an input 2D array. /// /// The type of elements in the input 2D array instance. /// The input 2D array instance. /// The target row to map within . /// The target column to map within . /// The height to map within . /// The width to map within . /// /// Thrown when doesn't match . /// /// /// Thrown when either , or /// are negative or not within the bounds that are valid for . /// /// A instance with the values of . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span2D AsSpan2D(this T[,]? array, int row, int column, int height, int width) { return new(array, row, column, height, width); } /// /// Creates a new over an input 2D array. /// /// The type of elements in the input 2D array instance. /// The input 2D array instance. /// A instance with the values of . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Memory2D AsMemory2D(this T[,]? array) { return new(array); } /// /// Creates a new over an input 2D array. /// /// The type of elements in the input 2D array instance. /// The input 2D array instance. /// The target row to map within . /// The target column to map within . /// The height to map within . /// The width to map within . /// /// Thrown when doesn't match . /// /// /// Thrown when either , or /// are negative or not within the bounds that are valid for . /// /// A instance with the values of . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Memory2D AsMemory2D(this T[,]? array, int row, int column, int height, int width) { return new(array, row, column, height, width); } #if NETSTANDARD2_1_OR_GREATER /// /// Returns a over a row in a given 2D array instance. /// /// The type of elements in the input 2D array instance. /// The input array instance. /// The target row to retrieve (0-based index). /// A with the items from the target row within . /// Thrown when doesn't match . /// Thrown when is invalid. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span GetRowSpan(this T[,] array, int row) { if (array.IsCovariant()) { ThrowArrayTypeMismatchException(); } if ((uint)row >= (uint)array.GetLength(0)) { ThrowArgumentOutOfRangeExceptionForRow(); } ref T r0 = ref array.DangerousGetReferenceAt(row, 0); return MemoryMarshal.CreateSpan(ref r0, array.GetLength(1)); } /// /// Returns a over a row in a given 2D array instance. /// /// The type of elements in the input 2D array instance. /// The input array instance. /// The target row to retrieve (0-based index). /// A with the items from the target row within . /// Thrown when doesn't match . /// Thrown when is invalid. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Memory GetRowMemory(this T[,] array, int row) { if (array.IsCovariant()) { ThrowArrayTypeMismatchException(); } if ((uint)row >= (uint)array.GetLength(0)) { ThrowArgumentOutOfRangeExceptionForRow(); } ref T r0 = ref array.DangerousGetReferenceAt(row, 0); IntPtr offset = ObjectMarshal.DangerousGetObjectDataByteOffset(array, ref r0); return new RawObjectMemoryManager(array, offset, array.GetLength(1)).Memory; } /// /// Creates a new over an input 2D array. /// /// The type of elements in the input 2D array instance. /// The input 2D array instance. /// A instance with the values of . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Memory AsMemory(this T[,]? array) { if (array is null) { return default; } if (array.IsCovariant()) { ThrowArrayTypeMismatchException(); } IntPtr offset = RuntimeHelpers.GetArray2DDataByteOffset(); int length = array.Length; return new RawObjectMemoryManager(array, offset, length).Memory; } /// /// Creates a new over an input 2D array. /// /// The type of elements in the input 2D array instance. /// The input 2D array instance. /// A instance with the values of . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span AsSpan(this T[,]? array) { if (array is null) { return default; } if (array.IsCovariant()) { ThrowArrayTypeMismatchException(); } ref T r0 = ref array.DangerousGetReference(); int length = array.Length; return MemoryMarshal.CreateSpan(ref r0, length); } #endif /// /// Counts the number of occurrences of a given value into a target 2D array instance. /// /// The type of items in the input 2D array instance. /// The input 2D array instance. /// The value to look for. /// The number of occurrences of in . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe int Count(this T[,] array, T value) where T : IEquatable { ref T r0 = ref array.DangerousGetReference(); nint length = RuntimeHelpers.GetArrayNativeLength(array); nint count = SpanHelper.Count(ref r0, length, value); if ((nuint)count > int.MaxValue) { ThrowOverflowException(); } return (int)count; } /// /// Gets a content hash from the input 2D array instance using the Djb2 algorithm. /// For more info, see the documentation for . /// /// The type of items in the input 2D array instance. /// The input 2D array instance. /// The Djb2 value for the input 2D array instance. /// The Djb2 hash is fully deterministic and with no random components. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe int GetDjb2HashCode(this T[,] array) where T : notnull { ref T r0 = ref array.DangerousGetReference(); nint length = RuntimeHelpers.GetArrayNativeLength(array); return SpanHelper.GetDjb2HashCode(ref r0, length); } /// /// Checks whether or not a given array is covariant. /// /// The type of items in the input array instance. /// The input array instance. /// Whether or not is covariant. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsCovariant(this T[,] array) { return default(T) is null && array.GetType() != typeof(T[,]); } /// /// Throws an when using an array of an invalid type. /// private static void ThrowArrayTypeMismatchException() { throw new ArrayTypeMismatchException("The given array doesn't match the specified type T."); } /// /// Throws an when the "row" parameter is invalid. /// private static void ThrowArgumentOutOfRangeExceptionForRow() { throw new ArgumentOutOfRangeException("row"); } /// /// Throws an when the "column" parameter is invalid. /// private static void ThrowArgumentOutOfRangeExceptionForColumn() { throw new ArgumentOutOfRangeException("column"); } }