// 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 NETCOREAPP3_1_OR_GREATER using System.Runtime.InteropServices; #endif using CommunityToolkit.HighPerformance.Enumerables; #if NETSTANDARD using CommunityToolkit.HighPerformance.Helpers; #endif using CommunityToolkit.HighPerformance.Helpers.Internals; using RuntimeHelpers = CommunityToolkit.HighPerformance.Helpers.Internals.RuntimeHelpers; namespace CommunityToolkit.HighPerformance; /// <summary> /// Helpers for working with the <see cref="Array"/> type. /// </summary> public static partial class ArrayExtensions { /// <summary> /// Returns a reference to the first element within a given <typeparamref name="T"/> array, with no bounds checks. /// </summary> /// <typeparam name="T">The type of elements in the input <typeparamref name="T"/> array instance.</typeparam> /// <param name="array">The input <typeparamref name="T"/> array instance.</param> /// <returns>A reference to the first element within <paramref name="array"/>, or the location it would have used, if <paramref name="array"/> is empty.</returns> /// <remarks>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.</remarks> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ref T DangerousGetReference<T>(this T[] array) { #if NET6_0_OR_GREATER return ref MemoryMarshal.GetArrayDataReference(array); #elif NETCOREAPP3_1 RawArrayData? arrayData = Unsafe.As<RawArrayData>(array)!; ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data); return ref r0; #else IntPtr offset = RuntimeHelpers.GetArrayDataByteOffset<T>(); return ref ObjectMarshal.DangerousGetObjectDataReferenceAt<T>(array, offset); #endif } /// <summary> /// Returns a reference to an element at a specified index within a given <typeparamref name="T"/> array, with no bounds checks. /// </summary> /// <typeparam name="T">The type of elements in the input <typeparamref name="T"/> array instance.</typeparam> /// <param name="array">The input <typeparamref name="T"/> array instance.</param> /// <param name="i">The index of the element to retrieve within <paramref name="array"/>.</param> /// <returns>A reference to the element within <paramref name="array"/> at the index specified by <paramref name="i"/>.</returns> /// <remarks>This method doesn't do any bounds checks, therefore it is responsibility of the caller to ensure the <paramref name="i"/> parameter is valid.</remarks> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ref T DangerousGetReferenceAt<T>(this T[] array, int i) { #if NET6_0_OR_GREATER ref T r0 = ref MemoryMarshal.GetArrayDataReference(array); ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)i); return ref ri; #elif NETCOREAPP3_1 RawArrayData? arrayData = Unsafe.As<RawArrayData>(array)!; ref T r0 = ref Unsafe.As<byte, T>(ref arrayData.Data); ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)i); return ref ri; #else IntPtr offset = RuntimeHelpers.GetArrayDataByteOffset<T>(); ref T r0 = ref ObjectMarshal.DangerousGetObjectDataReferenceAt<T>(array, offset); ref T ri = ref Unsafe.Add(ref r0, (nint)(uint)i); return ref ri; #endif } #if NETCOREAPP3_1 // Description taken from CoreCLR: see https://source.dot.net/#System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs,285. // CLR arrays are laid out in memory as follows (multidimensional array bounds are optional): // [ sync block || pMethodTable || num components || MD array bounds || array data .. ] // ^ ^ ^ returned reference // | \-- ref Unsafe.As<RawArrayData>(array).Data // \-- array // The base size of an array includes all the fields before the array data, // including the sync block and method table. The reference to RawData.Data // points at the number of components, skipping over these two pointer-sized fields. [StructLayout(LayoutKind.Sequential)] private sealed class RawArrayData { #pragma warning disable CS0649 // Unassigned fields public IntPtr Length; public byte Data; #pragma warning restore CS0649 } #endif /// <summary> /// Counts the number of occurrences of a given value into a target <typeparamref name="T"/> array instance. /// </summary> /// <typeparam name="T">The type of items in the input <typeparamref name="T"/> array instance.</typeparam> /// <param name="array">The input <typeparamref name="T"/> array instance.</param> /// <param name="value">The <typeparamref name="T"/> value to look for.</param> /// <returns>The number of occurrences of <paramref name="value"/> in <paramref name="array"/>.</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Count<T>(this T[] array, T value) where T : IEquatable<T> { 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; } /// <summary> /// Enumerates the items in the input <typeparamref name="T"/> array instance, as pairs of reference/index values. /// This extension should be used directly within a <see langword="foreach"/> loop: /// <code> /// int[] numbers = new[] { 1, 2, 3, 4, 5, 6, 7 }; /// /// foreach (var item in numbers.Enumerate()) /// { /// // Access the index and value of each item here... /// int index = item.Index; /// ref int value = ref item.Value; /// } /// </code> /// The compiler will take care of properly setting up the <see langword="foreach"/> loop with the type returned from this method. /// </summary> /// <typeparam name="T">The type of items to enumerate.</typeparam> /// <param name="array">The source <typeparamref name="T"/> array to enumerate.</param> /// <returns>A wrapper type that will handle the reference/index enumeration for <paramref name="array"/>.</returns> /// <remarks>The returned <see cref="SpanEnumerable{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static SpanEnumerable<T> Enumerate<T>(this T[] array) { return new(array); } /// <summary> /// Tokenizes the values in the input <typeparamref name="T"/> array instance using a specified separator. /// This extension should be used directly within a <see langword="foreach"/> loop: /// <code> /// char[] text = "Hello, world!".ToCharArray(); /// /// foreach (var token in text.Tokenize(',')) /// { /// // Access the tokens here... /// } /// </code> /// The compiler will take care of properly setting up the <see langword="foreach"/> loop with the type returned from this method. /// </summary> /// <typeparam name="T">The type of items in the <typeparamref name="T"/> array to tokenize.</typeparam> /// <param name="array">The source <typeparamref name="T"/> array to tokenize.</param> /// <param name="separator">The separator <typeparamref name="T"/> item to use.</param> /// <returns>A wrapper type that will handle the tokenization for <paramref name="array"/>.</returns> /// <remarks>The returned <see cref="SpanTokenizer{T}"/> value shouldn't be used directly: use this extension in a <see langword="foreach"/> loop.</remarks> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static SpanTokenizer<T> Tokenize<T>(this T[] array, T separator) where T : IEquatable<T> { return new(array, separator); } /// <summary> /// Gets a content hash from the input <typeparamref name="T"/> array instance using the Djb2 algorithm. /// For more info, see the documentation for <see cref="ReadOnlySpanExtensions.GetDjb2HashCode{T}"/>. /// </summary> /// <typeparam name="T">The type of items in the input <typeparamref name="T"/> array instance.</typeparam> /// <param name="array">The input <typeparamref name="T"/> array instance.</param> /// <returns>The Djb2 value for the input <typeparamref name="T"/> array instance.</returns> /// <remarks>The Djb2 hash is fully deterministic and with no random components.</remarks> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int GetDjb2HashCode<T>(this T[] array) where T : notnull { ref T r0 = ref array.DangerousGetReference(); nint length = RuntimeHelpers.GetArrayNativeLength(array); return SpanHelper.GetDjb2HashCode(ref r0, length); } /// <summary> /// Checks whether or not a given <typeparamref name="T"/> array is covariant. /// </summary> /// <typeparam name="T">The type of items in the input <typeparamref name="T"/> array instance.</typeparam> /// <param name="array">The input <typeparamref name="T"/> array instance.</param> /// <returns>Whether or not <paramref name="array"/> is covariant.</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsCovariant<T>(this T[] array) { return default(T) is null && array.GetType() != typeof(T[]); } /// <summary> /// Throws an <see cref="OverflowException"/> when the "column" parameter is invalid. /// </summary> private static void ThrowOverflowException() { throw new OverflowException(); } }