File: System\Runtime\Serialization\BinaryFormat\ArrayRecord.cs
Web Access
Project: src\src\libraries\System.Runtime.Serialization.BinaryFormat\src\System.Runtime.Serialization.BinaryFormat.csproj (System.Runtime.Serialization.BinaryFormat)
// 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.CodeAnalysis;
using System.Reflection.Metadata;
using System.Runtime.Serialization.BinaryFormat.Utils;
 
namespace System.Runtime.Serialization.BinaryFormat;
 
/// <summary>
/// Defines the core behavior for NRBF array records and provides a base for derived classes.
/// </summary>
#if SYSTEM_RUNTIME_SERIALIZATION_BINARYFORMAT
public
#else
internal
#endif
abstract class ArrayRecord : SerializationRecord
{
    private protected Array? _arrayNullsAllowed;
    private protected Array? _arrayNullsNotAllowed;
 
    private protected ArrayRecord(ArrayInfo arrayInfo)
    {
        ArrayInfo = arrayInfo;
        ValuesToRead = arrayInfo.TotalElementsCount;
    }
 
    /// <summary>
    /// When overridden in a derived class, gets a buffer of integers that represent the number of elements in every dimension.
    /// </summary>
    /// <value>A buffer of integers that represent the number of elements in every dimension.</value>
    public abstract ReadOnlySpan<int> Lengths { get; }
 
    /// <summary>
    /// Gets the rank of the array.
    /// </summary>
    /// <value>The rank of the array.</value>
    public int Rank => ArrayInfo.Rank;
 
    /// <summary>
    /// Gets the type of the array.
    /// </summary>
    /// <value>The type of the array.</value>
    public BinaryArrayType ArrayType => ArrayInfo.ArrayType;
 
    /// <summary>
    /// Gets the name of the array element type.
    /// </summary>
    /// <value>The name of the array element type.</value>
    public abstract TypeName ElementTypeName { get; }
 
    /// <inheritdoc />
    public override int ObjectId => ArrayInfo.ObjectId;
 
    internal long ValuesToRead { get; private protected set; }
 
    private protected ArrayInfo ArrayInfo { get; }
 
    /// <summary>
    /// Allocates an array and fills it with the data provided in the serialized records (in case of primitive types like <see cref="string"/> or <see cref="int"/>) or the serialized records themselves.
    /// </summary>
    /// <param name="expectedArrayType">Expected array type.</param>
    /// <param name="allowNulls">
    ///   <see langword="true" /> to permit <see langword="null" /> values within the array;
    ///   otherwise, <see langword="false" />.
    /// </param>
    /// <returns>An array filled with the data provided in the serialized records.</returns>
    /// <exception cref="InvalidOperationException"><paramref name="expectedArrayType" /> does not match the data from the payload.</exception>
    [RequiresDynamicCode("The code for an array of the specified type might not be available.")]
    public Array GetArray(Type expectedArrayType, bool allowNulls = true)
    {
#if NET
        ArgumentNullException.ThrowIfNull(expectedArrayType);
#else
        if (expectedArrayType is null)
        {
            throw new ArgumentNullException(nameof(expectedArrayType));
        }
#endif
        if (!IsTypeNameMatching(expectedArrayType))
        {
            throw new InvalidOperationException(SR.Format(SR.Serialization_TypeMismatch, expectedArrayType.AssemblyQualifiedName, ElementTypeName.AssemblyQualifiedName));
        }
 
        return allowNulls
            ? _arrayNullsAllowed ??= Deserialize(expectedArrayType, true)
            : _arrayNullsNotAllowed ??= Deserialize(expectedArrayType, false);
    }
 
    [RequiresDynamicCode("May call Array.CreateInstance() and Type.MakeArrayType().")]
    private protected abstract Array Deserialize(Type arrayType, bool allowNulls);
 
    public override bool IsTypeNameMatching(Type type)
        => type.IsArray
        && type.GetArrayRank() == ArrayInfo.Rank
        && IsElementType(type.GetElementType()!);
 
    internal sealed override void HandleNextValue(object value, NextInfo info)
        => HandleNext(value, info, size: 1);
 
    internal sealed override void HandleNextRecord(SerializationRecord nextRecord, NextInfo info)
        => HandleNext(nextRecord, info, size: nextRecord is NullsRecord nullsRecord ? nullsRecord.NullCount : 1);
 
    private protected abstract void AddValue(object value);
 
    internal abstract bool IsElementType(Type typeElement);
 
    private void HandleNext(object value, NextInfo info, int size)
    {
        ValuesToRead -= size;
 
        if (ValuesToRead < 0)
        {
            // The only way to get here is to read a multiple null item with Count
            // larger than the number of array items that were left to read.
            ThrowHelper.ThrowUnexpectedNullRecordCount();
        }
        else if (ValuesToRead > 0)
        {
            info.Stack.Push(info);
        }
 
        AddValue(value);
    }
 
    internal abstract (AllowedRecordTypes allowed, PrimitiveType primitiveType) GetAllowedRecordType();
}
 
/// <summary>
/// Defines the core behavior for NRBF single dimensional, zero-indexed array records and provides a base for derived classes.
/// </summary>
/// <typeparam name="T"></typeparam>
#if SYSTEM_RUNTIME_SERIALIZATION_BINARYFORMAT
public
#else
internal
#endif
abstract class ArrayRecord<T> : ArrayRecord
{
    private protected ArrayRecord(ArrayInfo arrayInfo) : base(arrayInfo)
    {
    }
 
    /// <summary>
    /// Gets the length of the array.
    /// </summary>
    /// <value>The length of the array.</value>
    public int Length => ArrayInfo.GetSZArrayLength();
 
    /// <inheritdoc/>
    public override ReadOnlySpan<int> Lengths => new int[1] { Length };
 
    /// <summary>
    /// When overridden in a derived class, allocates an array of <typeparamref name="T"/> and fills it with the data provided in the serialized records (in case of primitive types like <see cref="string"/> or <see cref="int"/>) or the serialized records themselves.
    /// </summary>
    /// <param name="allowNulls">
    ///   <see langword="true" /> to permit <see langword="null" /> values within the array;
    ///   otherwise, <see langword="false" />.
    /// </param>
    /// <returns>An array filled with the data provided in the serialized records.</returns>
    public abstract T?[] GetArray(bool allowNulls = true);
 
#pragma warning disable IL3051 // RequiresDynamicCode is not required in this particualar case
    private protected override Array Deserialize(Type arrayType, bool allowNulls) => GetArray(allowNulls);
#pragma warning restore IL3051
}