File: System\Resources\Extensions\BinaryFormat\Deserializer\ArrayRecordDeserializer.cs
Web Access
Project: src\src\libraries\System.Resources.Extensions\src\System.Resources.Extensions.csproj (System.Resources.Extensions)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.Serialization.BinaryFormat;
 
namespace System.Resources.Extensions.BinaryFormat.Deserializer;
 
internal sealed class ArrayRecordDeserializer : ObjectRecordDeserializer
{
    private readonly ArrayRecord _arrayRecord;
    private readonly Type _elementType;
    private readonly Array _arrayOfClassRecords;
    private readonly Array _arrayOfT;
    private readonly int[] _lengths, _indices;
    private bool _hasFixups, _canIterate;
 
    [RequiresUnreferencedCode("Calls System.Windows.Forms.BinaryFormat.BinaryFormattedObject.TypeResolver.GetType(TypeName)")]
    internal ArrayRecordDeserializer(ArrayRecord arrayRecord, IDeserializer deserializer)
        : base(arrayRecord, deserializer)
    {
        // Other array types are handled directly (ArraySinglePrimitive and ArraySingleString).
        Debug.Assert(arrayRecord.RecordType is not (RecordType.ArraySingleString or RecordType.ArraySinglePrimitive));
        Debug.Assert(arrayRecord.ArrayType is (BinaryArrayType.Single or BinaryArrayType.Jagged or BinaryArrayType.Rectangular));
 
        _arrayRecord = arrayRecord;
        _elementType = deserializer.TypeResolver.GetType(arrayRecord.ElementTypeName);
        Type expectedArrayType = arrayRecord.ArrayType switch
        {
            BinaryArrayType.Rectangular => _elementType.MakeArrayType(arrayRecord.Rank),
            _ => _elementType.MakeArrayType()
        };
        // Tricky part: for arrays of classes/structs the following record allocates and array of class records
        // (because the payload reader can not load types, instantiate objects and rehydrate them)
        _arrayOfClassRecords = arrayRecord.GetArray(expectedArrayType);
        // Now we need to create an array of the same length, but of a different, exact type
        Type elementType = _arrayOfClassRecords.GetType();
        while (elementType.IsArray)
        {
            elementType = elementType.GetElementType()!;
        }
 
        _lengths = arrayRecord.Lengths.ToArray();
        Object = _arrayOfT = Array.CreateInstance(_elementType, _lengths);
        _indices = new int[_lengths.Length];
        _canIterate = _arrayOfT.Length > 0;
    }
 
    internal override Id Continue()
    {
        int[] indices = _indices;
        int[] lengths = _lengths;
 
        while (_canIterate)
        {
            (object? memberValue, Id reference) = UnwrapMemberValue(_arrayOfClassRecords.GetValue(indices));
 
            if (s_missingValueSentinel == memberValue)
            {
                // Record has not been encountered yet, need to pend iteration.
                return reference;
            }
 
            if (memberValue is not null && DoesValueNeedUpdated(memberValue, reference))
            {
                // Need to track a fixup for this index.
                _hasFixups = true;
                Deserializer.PendValueUpdater(new ArrayUpdater(_arrayRecord.ObjectId, reference, indices.ToArray()));
            }
 
            _arrayOfT.SetValue(memberValue, indices);
 
            int dimension = indices.Length - 1;
            while (dimension >= 0)
            {
                indices[dimension]++;
                if (indices[dimension] < lengths[dimension])
                {
                    break;
                }
 
                indices[dimension] = 0;
                dimension--;
            }
 
            if (dimension < 0)
            {
                _canIterate = false;
            }
        }
 
        // No more missing member refs.
 
        if (!_hasFixups)
        {
            Deserializer.CompleteObject(_arrayRecord.ObjectId);
        }
 
        return Id.Null;
    }
 
    internal static Array GetArraySinglePrimitive(SerializationRecord record) => record switch
    {
        ArrayRecord<bool> primitiveArray => primitiveArray.GetArray(),
        ArrayRecord<byte> primitiveArray => primitiveArray.GetArray(),
        ArrayRecord<sbyte> primitiveArray => primitiveArray.GetArray(),
        ArrayRecord<char> primitiveArray => primitiveArray.GetArray(),
        ArrayRecord<short> primitiveArray => primitiveArray.GetArray(),
        ArrayRecord<ushort> primitiveArray => primitiveArray.GetArray(),
        ArrayRecord<int> primitiveArray => primitiveArray.GetArray(),
        ArrayRecord<uint> primitiveArray => primitiveArray.GetArray(),
        ArrayRecord<long> primitiveArray => primitiveArray.GetArray(),
        ArrayRecord<ulong> primitiveArray => primitiveArray.GetArray(),
        ArrayRecord<float> primitiveArray => primitiveArray.GetArray(),
        ArrayRecord<double> primitiveArray => primitiveArray.GetArray(),
        ArrayRecord<decimal> primitiveArray => primitiveArray.GetArray(),
        ArrayRecord<DateTime> primitiveArray => primitiveArray.GetArray(),
        ArrayRecord<TimeSpan> primitiveArray => primitiveArray.GetArray(),
        _ => throw new NotSupportedException(),
    };
 
    [RequiresUnreferencedCode("Calls System.Windows.Forms.BinaryFormat.BinaryFormattedObject.TypeResolver.GetType(TypeName)")]
    internal static Array? GetSimpleBinaryArray(ArrayRecord arrayRecord, BinaryFormattedObject.ITypeResolver typeResolver)
    {
        if (arrayRecord.ArrayType is not (BinaryArrayType.Single or BinaryArrayType.Jagged or BinaryArrayType.Rectangular))
        {
            throw new NotSupportedException(SR.NotSupported_NonZeroOffsets);
        }
 
        Type arrayRecordElementType = typeResolver.GetType(arrayRecord.ElementTypeName);
        Type elementType = arrayRecordElementType;
        while (elementType.IsArray)
        {
            elementType = elementType.GetElementType()!;
        }
 
        if (!(HasBuiltInSupport(elementType)
            || (Nullable.GetUnderlyingType(elementType) is Type nullable && HasBuiltInSupport(nullable))))
        {
            return null;
        }
 
        Type expectedArrayType = arrayRecord.ArrayType switch
        {
            BinaryArrayType.Rectangular => arrayRecordElementType.MakeArrayType(arrayRecord.Rank),
            _ => arrayRecordElementType.MakeArrayType()
        };
 
        return arrayRecord.GetArray(expectedArrayType);
 
        static bool HasBuiltInSupport(Type elementType)
            => elementType == typeof(string)
            || elementType == typeof(bool) || elementType == typeof(byte) || elementType == typeof(sbyte)
            || elementType == typeof(char) || elementType == typeof(short) || elementType == typeof(ushort)
            || elementType == typeof(int) || elementType == typeof(uint)
            || elementType == typeof(long) || elementType == typeof(ulong)
            || elementType == typeof(float) || elementType == typeof(double) || elementType == typeof(decimal)
            || elementType == typeof(DateTime) || elementType == typeof(TimeSpan);
    }
}