< Summary

Information
Class: AmbientServices.Utilities.ArrayUtilities
Assembly: AmbientServices
File(s): /home/runner/work/AmbientServices/AmbientServices/AmbientServices/Utilities/ArrayUtilities.cs
Tag: 332_35464845198
Line coverage
100%
Covered lines: 54
Uncovered lines: 0
Coverable lines: 54
Total lines: 127
Line coverage: 100%
Branch coverage
94%
Covered branches: 32
Total branches: 34
Branch coverage: 94.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
ValueEquals(...)100%1818100%
ValueHashCode(...)100%11100%
ValueHashCode(...)87.5%1616100%

File(s)

/home/runner/work/AmbientServices/AmbientServices/AmbientServices/Utilities/ArrayUtilities.cs

#LineLine coverage
 1using System;
 2
 3namespace AmbientServices.Utilities;
 4
 5/// <summary>
 6/// A static class that adds utilities for <see cref="System.Array"/>.
 7/// </summary>
 8/// <remarks>
 9/// <pitch>The comparison and hashing engine behind <see cref="AmbientServices.Extensions.ArrayExtensions"/>: deep value
 10/// <pledge>Arrays are equal only when they agree in rank and in length in every dimension and every corresponding pair 
 11/// <plan>Multidimensional arrays are walked without recursion by mapping a single linear offset to a per-dimension curs
 12/// </remarks>
 13internal static class ArrayUtilities
 14{
 15    /// <summary>The value returned for a nested array deeper than <see cref="MaxHashRecursionDepth"/>; a defensive back
 16    private const int CycleHashMarker = 0x0C0FFEE;
 17    /// <summary>The maximum nested-array recursion depth for <see cref="ValueHashCode(Type, Array?)"/>; far deeper than
 18    private const int MaxHashRecursionDepth = 64;
 19
 20    /// <summary>
 21    /// Checks to see if the contents of two arrays are equal.
 22    /// </summary>
 23    /// <param name="elementType">The type of items in the array.</param>
 24    /// <param name="array1">The first array.</param>
 25    /// <param name="array2">The second array.</param>
 26    /// <returns>Whether or not the content of the arrays are equal.</returns>
 27    public static bool ValueEquals(Type elementType, Array? array1, Array? array2)
 28    {
 29#if NET5_0_OR_GREATER
 230        ArgumentNullException.ThrowIfNull(elementType);
 31#else
 32        if (elementType is null) throw new ArgumentNullException(nameof(elementType));
 33#endif
 234        if (array1 == null)
 35        {
 236            return array2 == null;
 37        }
 238        else if (array2 == null)
 39        {
 240            return false;
 41        }
 42        // compare ranks
 243        int rank = array1.Rank;
 244        if (rank != array2.Rank) return false;
 45        // create a cursor and figure out how many items are contained within each dimension and every dimension below
 246        long[] cursor = new long[rank];
 247        long[] size = new long[rank + 1];
 248        size[rank] = 1;
 249        for (int dimension = rank - 1; dimension >= 0; --dimension)                // for example: [10,8,5]--> [10*8*5,8
 50        {
 251            cursor[dimension] = 0;
 252            int array1DimensionLength = array1.GetLength(dimension);
 253            int array2DimensionLength = array2.GetLength(dimension);
 54            // lengths differ in this dimension?
 255            if (array1DimensionLength != array2DimensionLength) return false;
 256            size[dimension] = array1DimensionLength * size[dimension + 1];
 57        }
 58        // now loop through the arrays comparing each item
 259        for (long offset = 0; offset < size[0]; ++offset)
 60        {
 261            long remainder = offset;
 262            for (int dimension = 0; dimension < rank; ++dimension)
 63            {
 264                cursor[dimension] = remainder / size[dimension + 1];
 265                remainder %= size[dimension + 1];
 66            }
 267            bool eq = (elementType.IsArray)
 268                    // I could be wrong, but I'm pretty sure if elementType.IsArray is true, GetElementType() cannot ret
 269                ? ValueEquals(elementType.GetElementType()!, (Array?)array1.GetValue(cursor), (Array?)array2.GetValue(cu
 270                : Equals(array1.GetValue(cursor), array2.GetValue(cursor));
 271            if (!eq) return false;
 72        }
 73        // they are equal!
 274        return true;
 75    }
 76    /// <summary>
 77    /// Gets a value-based hash code for an array, recursing into nested (jagged/multidimensional) element arrays so the
 78    /// </summary>
 79    /// <param name="elementType">The declared element type of the array.</param>
 80    /// <param name="array">The array to hash, or null.</param>
 81    /// <returns>A value-based hash code, or zero for a null array.</returns>
 82    public static int ValueHashCode(Type elementType, Array? array)
 83    {
 84#if NET5_0_OR_GREATER
 285        ArgumentNullException.ThrowIfNull(elementType);
 86#else
 87        if (elementType is null) throw new ArgumentNullException(nameof(elementType));
 88#endif
 289        return ValueHashCode(elementType, array, 0);
 90    }
 91    private static int ValueHashCode(Type elementType, Array? array, int depth)
 92    {
 293        if (array == null) return 0;
 94        // recursion guard: bound the nested-array depth so a pathological input cannot recurse without limit (valid jag
 295        if (depth > MaxHashRecursionDepth) return CycleHashMarker;
 296        int rank = array.Rank;
 297        int code = rank;
 298        long[] cursor = new long[rank];
 299        long[] size = new long[rank + 1];
 2100        size[rank] = 1;
 2101        for (int dimension = rank - 1; dimension >= 0; --dimension)
 102        {
 2103            int dimensionLength = array.GetLength(dimension);
 2104            code = code * 31 + dimensionLength;    // fold each dimension's length into the hash so different shapes has
 2105            size[dimension] = dimensionLength * size[dimension + 1];
 106        }
 2107        bool elementIsArray = elementType.IsArray;
 2108        Type? nestedElementType = elementIsArray ? elementType.GetElementType() : null;
 109        // walk every element via the same linear-offset/cursor scheme as ValueEquals, recursing into nested arrays exac
 2110        for (long offset = 0; offset < size[0]; ++offset)
 111        {
 2112            long remainder = offset;
 2113            for (int dimension = 0; dimension < rank; ++dimension)
 114            {
 2115                cursor[dimension] = remainder / size[dimension + 1];
 2116                remainder %= size[dimension + 1];
 117            }
 2118            object? element = array.GetValue(cursor);
 2119            int elemhashcode = elementIsArray
 2120                ? ValueHashCode(nestedElementType!, (Array?)element, depth + 1)
 2121                : (element?.GetHashCode() ?? 0);
 2122            int shift = (int)(offset % 32);
 2123            code ^= (elemhashcode >> (32 - shift)) ^ (elemhashcode << shift) ^ 0x1A7FCA3B;
 124        }
 2125        return code;
 126    }
 127}