File: System\Runtime\Serialization\Formatters\Binary\BinaryFormatterWriter.cs
Web Access
Project: src\src\libraries\System.Runtime.Serialization.Formatters\src\System.Runtime.Serialization.Formatters.csproj (System.Runtime.Serialization.Formatters)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
 
namespace System.Runtime.Serialization.Formatters.Binary
{
    internal sealed class BinaryFormatterWriter
    {
        private const int ChunkSize = 4096;
 
        private readonly Stream _outputStream;
        private readonly FormatterTypeStyle _formatterTypeStyle;
        private readonly ObjectWriter _objectWriter;
        private readonly BinaryWriter _dataWriter;
 
        private int _consecutiveNullArrayEntryCount;
        private Dictionary<string, ObjectMapInfo>? _objectMapTable;
 
        private BinaryObject? _binaryObject;
        private BinaryObjectWithMap? _binaryObjectWithMap;
        private BinaryObjectWithMapTyped? _binaryObjectWithMapTyped;
        private BinaryObjectString? _binaryObjectString;
        private BinaryArray? _binaryArray;
        private byte[]? _byteBuffer;
        private MemberPrimitiveUnTyped? _memberPrimitiveUnTyped;
        private MemberPrimitiveTyped? _memberPrimitiveTyped;
        private ObjectNull? _objectNull;
        private MemberReference? _memberReference;
        private BinaryAssembly? _binaryAssembly;
 
        internal BinaryFormatterWriter(Stream outputStream, ObjectWriter objectWriter, FormatterTypeStyle formatterTypeStyle)
        {
            _outputStream = outputStream;
            _formatterTypeStyle = formatterTypeStyle;
            _objectWriter = objectWriter;
            _dataWriter = new BinaryWriter(outputStream, Encoding.UTF8);
        }
 
        internal void WriteBegin() { }
 
        internal void WriteEnd()
        {
            _dataWriter.Flush();
        }
 
        internal void WriteBoolean(bool value) => _dataWriter.Write(value);
 
        internal void WriteByte(byte value) => _dataWriter.Write(value);
 
        private void WriteBytes(byte[] value) => _dataWriter.Write(value);
 
        private void WriteBytes(byte[] byteA, int offset, int size) => _dataWriter.Write(byteA, offset, size);
 
        internal void WriteChar(char value) => _dataWriter.Write(value);
 
        internal void WriteChars(char[] value) => _dataWriter.Write(value);
 
        internal void WriteDecimal(decimal value) => WriteString(value.ToString(CultureInfo.InvariantCulture));
 
        internal void WriteSingle(float value) => _dataWriter.Write(value);
 
        internal void WriteDouble(double value) => _dataWriter.Write(value);
 
        internal void WriteInt16(short value) => _dataWriter.Write(value);
 
        internal void WriteInt32(int value) => _dataWriter.Write(value);
 
        internal void WriteInt64(long value) => _dataWriter.Write(value);
 
        internal void WriteSByte(sbyte value) => WriteByte(unchecked((byte)value));
 
        internal void WriteString(string value) => _dataWriter.Write(value);
 
        internal void WriteTimeSpan(TimeSpan value) => WriteInt64(value.Ticks);
 
        internal void WriteDateTime(DateTime value)
        {
            // In .NET Framework, BinaryFormatter is able to access DateTime's ToBinaryRaw,
            // which just returns the value of its sole Int64 dateData field.  Here, we don't
            // have access to that member (which doesn't even exist anymore, since it was only for
            // BinaryFormatter, which is now in a separate assembly).  To address that,
            // we access the sole field directly via an unsafe cast.
            long dateData = Unsafe.As<DateTime, long>(ref value);
            WriteInt64(dateData);
        }
 
        internal void WriteUInt16(ushort value) => _dataWriter.Write(value);
 
        internal void WriteUInt32(uint value) => _dataWriter.Write(value);
 
        internal void WriteUInt64(ulong value) => _dataWriter.Write(value);
 
        internal void WriteObjectEnd(NameInfo memberNameInfo, NameInfo typeNameInfo) { }
 
        internal void WriteSerializationHeaderEnd()
        {
            var record = new MessageEnd();
            record.Write(this);
        }
 
        internal void WriteSerializationHeader(int topId, int headerId, int minorVersion, int majorVersion)
        {
            var record = new SerializationHeaderRecord(BinaryHeaderEnum.SerializedStreamHeader, topId, headerId, minorVersion, majorVersion);
            record.Write(this);
        }
 
        internal void WriteObject(NameInfo nameInfo, NameInfo? typeNameInfo, int numMembers, string[] memberNames, Type[] memberTypes, WriteObjectInfo[] memberObjectInfos)
        {
            InternalWriteItemNull();
            int assemId;
            int objectId = (int)nameInfo._objectId;
 
            Debug.Assert(typeNameInfo != null); // Explicitly called with null. Potential bug, but closed as Won't Fix: https://github.com/dotnet/runtime/issues/31402
            string? objectName = objectId < 0 ?
                typeNameInfo.NIname : // Nested Object
                nameInfo.NIname; // Non-Nested
 
            _objectMapTable ??= new Dictionary<string, ObjectMapInfo>();
 
            Debug.Assert(objectName != null);
            if (_objectMapTable.TryGetValue(objectName, out ObjectMapInfo? objectMapInfo) &&
                objectMapInfo.IsCompatible(numMembers, memberNames, memberTypes))
            {
                // Object
                _binaryObject ??= new BinaryObject();
 
                _binaryObject.Set(objectId, objectMapInfo._objectId);
                _binaryObject.Write(this);
            }
            else if (!typeNameInfo._transmitTypeOnObject)
            {
                // ObjectWithMap
                _binaryObjectWithMap ??= new BinaryObjectWithMap();
 
                // BCL types are not placed into table
                assemId = (int)typeNameInfo._assemId;
                _binaryObjectWithMap.Set(objectId, objectName, numMembers, memberNames, assemId);
 
                _binaryObjectWithMap.Write(this);
                if (objectMapInfo == null)
                {
                    _objectMapTable.Add(objectName, new ObjectMapInfo(objectId, numMembers, memberNames, memberTypes));
                }
            }
            else
            {
                // ObjectWithMapTyped
                var binaryTypeEnumA = new BinaryTypeEnum[numMembers];
                var typeInformationA = new object?[numMembers];
                var assemIdA = new int[numMembers];
                for (int i = 0; i < numMembers; i++)
                {
                    object? typeInformation;
                    binaryTypeEnumA[i] = BinaryTypeConverter.GetBinaryTypeInfo(memberTypes[i], memberObjectInfos[i], null, _objectWriter, out typeInformation, out assemId);
                    typeInformationA[i] = typeInformation;
                    assemIdA[i] = assemId;
                }
 
                _binaryObjectWithMapTyped ??= new BinaryObjectWithMapTyped();
 
                // BCL types are not placed in table
                assemId = (int)typeNameInfo._assemId;
                _binaryObjectWithMapTyped.Set(objectId, objectName, numMembers, memberNames, binaryTypeEnumA, typeInformationA, assemIdA, assemId);
                _binaryObjectWithMapTyped.Write(this);
                if (objectMapInfo == null)
                {
                    _objectMapTable.Add(objectName, new ObjectMapInfo(objectId, numMembers, memberNames, memberTypes));
                }
            }
        }
 
        internal void WriteObjectString(int objectId, string? value)
        {
            InternalWriteItemNull();
 
            _binaryObjectString ??= new BinaryObjectString();
 
            _binaryObjectString.Set(objectId, value);
            _binaryObjectString.Write(this);
        }
 
        internal void WriteSingleArray(NameInfo memberNameInfo, NameInfo arrayNameInfo, WriteObjectInfo? objectInfo, NameInfo arrayElemTypeNameInfo, int length, int lowerBound, Array array)
        {
            InternalWriteItemNull();
            BinaryArrayTypeEnum binaryArrayTypeEnum;
            var lengthA = new int[1];
            lengthA[0] = length;
            int[]? lowerBoundA = null;
            object? typeInformation;
 
            if (lowerBound == 0)
            {
                binaryArrayTypeEnum = BinaryArrayTypeEnum.Single;
            }
            else
            {
                binaryArrayTypeEnum = BinaryArrayTypeEnum.SingleOffset;
                lowerBoundA = new int[1];
                lowerBoundA[0] = lowerBound;
            }
 
            int assemId;
            BinaryTypeEnum binaryTypeEnum = BinaryTypeConverter.GetBinaryTypeInfo(
                arrayElemTypeNameInfo._type!, objectInfo, arrayElemTypeNameInfo.NIname, _objectWriter, out typeInformation, out assemId);
 
            _binaryArray ??= new BinaryArray();
            _binaryArray.Set((int)arrayNameInfo._objectId, 1, lengthA, lowerBoundA, binaryTypeEnum, typeInformation, binaryArrayTypeEnum, assemId);
 
            _binaryArray.Write(this);
 
            if (Converter.IsWriteAsByteArray(arrayElemTypeNameInfo._primitiveTypeEnum) && (lowerBound == 0))
            {
                //array is written out as an array of bytes
                if (arrayElemTypeNameInfo._primitiveTypeEnum == InternalPrimitiveTypeE.Byte)
                {
                    WriteBytes((byte[])array);
                }
                else if (arrayElemTypeNameInfo._primitiveTypeEnum == InternalPrimitiveTypeE.Char)
                {
                    WriteChars((char[])array);
                }
                else
                {
                    WriteArrayAsBytes(array, Converter.TypeLength(arrayElemTypeNameInfo._primitiveTypeEnum));
                }
            }
        }
 
        private void WriteArrayAsBytes(Array array, int typeLength)
        {
            InternalWriteItemNull();
            int arrayOffset = 0;
            _byteBuffer ??= new byte[ChunkSize];
 
            while (arrayOffset < array.Length)
            {
                int numArrayItems = Math.Min(ChunkSize / typeLength, array.Length - arrayOffset);
                int bufferUsed = numArrayItems * typeLength;
                Buffer.BlockCopy(array, arrayOffset * typeLength, _byteBuffer, 0, bufferUsed);
                if (!BitConverter.IsLittleEndian)
                {
                    // we know that we are writing a primitive type, so just do a simple swap
                    for (int i = 0; i < bufferUsed; i += typeLength)
                    {
                        for (int j = 0; j < typeLength / 2; j++)
                        {
                            byte tmp = _byteBuffer[i + j];
                            _byteBuffer[i + j] = _byteBuffer[i + typeLength - 1 - j];
                            _byteBuffer[i + typeLength - 1 - j] = tmp;
                        }
                    }
                }
                WriteBytes(_byteBuffer, 0, bufferUsed);
                arrayOffset += numArrayItems;
            }
        }
 
        internal void WriteJaggedArray(NameInfo memberNameInfo, NameInfo arrayNameInfo, WriteObjectInfo? objectInfo, NameInfo arrayElemTypeNameInfo, int length, int lowerBound)
        {
            InternalWriteItemNull();
            BinaryArrayTypeEnum binaryArrayTypeEnum;
            var lengthA = new int[1];
            lengthA[0] = length;
            int[]? lowerBoundA = null;
            object? typeInformation;
            int assemId;
 
            if (lowerBound == 0)
            {
                binaryArrayTypeEnum = BinaryArrayTypeEnum.Jagged;
            }
            else
            {
                binaryArrayTypeEnum = BinaryArrayTypeEnum.JaggedOffset;
                lowerBoundA = new int[1];
                lowerBoundA[0] = lowerBound;
            }
 
            BinaryTypeEnum binaryTypeEnum = BinaryTypeConverter.GetBinaryTypeInfo(arrayElemTypeNameInfo._type!, objectInfo, arrayElemTypeNameInfo.NIname, _objectWriter, out typeInformation, out assemId);
 
            _binaryArray ??= new BinaryArray();
            _binaryArray.Set((int)arrayNameInfo._objectId, 1, lengthA, lowerBoundA, binaryTypeEnum, typeInformation, binaryArrayTypeEnum, assemId);
 
            _binaryArray.Write(this);
        }
 
        internal void WriteRectangleArray(NameInfo memberNameInfo, NameInfo arrayNameInfo, WriteObjectInfo? objectInfo, NameInfo arrayElemTypeNameInfo, int rank, int[] lengthA, int[] lowerBoundA)
        {
            InternalWriteItemNull();
 
            BinaryArrayTypeEnum binaryArrayTypeEnum = BinaryArrayTypeEnum.Rectangular;
            object? typeInformation;
            int assemId;
            BinaryTypeEnum binaryTypeEnum = BinaryTypeConverter.GetBinaryTypeInfo(arrayElemTypeNameInfo._type!, objectInfo, arrayElemTypeNameInfo.NIname, _objectWriter, out typeInformation, out assemId);
 
            _binaryArray ??= new BinaryArray();
 
            for (int i = 0; i < rank; i++)
            {
                if (lowerBoundA[i] != 0)
                {
                    binaryArrayTypeEnum = BinaryArrayTypeEnum.RectangularOffset;
                    break;
                }
            }
 
            _binaryArray.Set((int)arrayNameInfo._objectId, rank, lengthA, lowerBoundA, binaryTypeEnum, typeInformation, binaryArrayTypeEnum, assemId);
            _binaryArray.Write(this);
        }
 
        internal void WriteObjectByteArray(NameInfo memberNameInfo, NameInfo arrayNameInfo, WriteObjectInfo? objectInfo, NameInfo arrayElemTypeNameInfo, int length, int lowerBound, byte[] byteA)
        {
            InternalWriteItemNull();
            WriteSingleArray(memberNameInfo, arrayNameInfo, objectInfo, arrayElemTypeNameInfo, length, lowerBound, byteA);
        }
 
        internal void WriteMember(NameInfo memberNameInfo, NameInfo typeNameInfo, object value)
        {
            InternalWriteItemNull();
            InternalPrimitiveTypeE typeInformation = typeNameInfo._primitiveTypeEnum;
 
            // Writes Members with primitive values
            if (memberNameInfo._transmitTypeOnMember)
            {
                _memberPrimitiveTyped ??= new MemberPrimitiveTyped();
                _memberPrimitiveTyped.Set(typeInformation, value);
                _memberPrimitiveTyped.Write(this);
            }
            else
            {
                _memberPrimitiveUnTyped ??= new MemberPrimitiveUnTyped();
                _memberPrimitiveUnTyped.Set(typeInformation, value);
                _memberPrimitiveUnTyped.Write(this);
            }
        }
 
        internal void WriteNullMember(NameInfo memberNameInfo, NameInfo typeNameInfo)
        {
            InternalWriteItemNull();
            _objectNull ??= new ObjectNull();
 
            if (!memberNameInfo._isArrayItem)
            {
                _objectNull.SetNullCount(1);
                _objectNull.Write(this);
                _consecutiveNullArrayEntryCount = 0;
            }
        }
 
        internal void WriteMemberObjectRef(NameInfo memberNameInfo, int idRef)
        {
            InternalWriteItemNull();
            _memberReference ??= new MemberReference();
            _memberReference.Set(idRef);
            _memberReference.Write(this);
        }
 
        internal void WriteMemberNested(NameInfo memberNameInfo)
        {
            InternalWriteItemNull();
        }
 
        internal void WriteMemberString(NameInfo memberNameInfo, NameInfo typeNameInfo, string? value)
        {
            InternalWriteItemNull();
            WriteObjectString((int)typeNameInfo._objectId, value);
        }
 
        internal void WriteItem(NameInfo itemNameInfo, NameInfo typeNameInfo, object value)
        {
            InternalWriteItemNull();
            WriteMember(itemNameInfo, typeNameInfo, value);
        }
 
        internal void WriteNullItem(NameInfo itemNameInfo, NameInfo typeNameInfo)
        {
            _consecutiveNullArrayEntryCount++;
            InternalWriteItemNull();
        }
 
        internal void WriteDelayedNullItem()
        {
            _consecutiveNullArrayEntryCount++;
        }
 
        internal void WriteItemEnd() => InternalWriteItemNull();
 
        private void InternalWriteItemNull()
        {
            if (_consecutiveNullArrayEntryCount > 0)
            {
                _objectNull ??= new ObjectNull();
                _objectNull.SetNullCount(_consecutiveNullArrayEntryCount);
                _objectNull.Write(this);
                _consecutiveNullArrayEntryCount = 0;
            }
        }
 
        internal void WriteItemObjectRef(NameInfo nameInfo, int idRef)
        {
            InternalWriteItemNull();
            WriteMemberObjectRef(nameInfo, idRef);
        }
 
        internal void WriteAssembly(Type? type, string assemblyString, int assemId, bool isNew)
        {
            //If the file being tested wasn't built as an assembly, then we're going to get null back
            //for the assembly name.  This is very unfortunate.
            InternalWriteItemNull();
            assemblyString ??= string.Empty;
 
            if (isNew)
            {
                _binaryAssembly ??= new BinaryAssembly();
                _binaryAssembly.Set(assemId, assemblyString);
                _binaryAssembly.Write(this);
            }
        }
 
        // Method to write a value onto a stream given its primitive type code
        internal void WriteValue(InternalPrimitiveTypeE code, object? value)
        {
            switch (code)
            {
                case InternalPrimitiveTypeE.Boolean: WriteBoolean(Convert.ToBoolean(value, CultureInfo.InvariantCulture)); break;
                case InternalPrimitiveTypeE.Byte: WriteByte(Convert.ToByte(value, CultureInfo.InvariantCulture)); break;
                case InternalPrimitiveTypeE.Char: WriteChar(Convert.ToChar(value, CultureInfo.InvariantCulture)); break;
                case InternalPrimitiveTypeE.Double: WriteDouble(Convert.ToDouble(value, CultureInfo.InvariantCulture)); break;
                case InternalPrimitiveTypeE.Int16: WriteInt16(Convert.ToInt16(value, CultureInfo.InvariantCulture)); break;
                case InternalPrimitiveTypeE.Int32: WriteInt32(Convert.ToInt32(value, CultureInfo.InvariantCulture)); break;
                case InternalPrimitiveTypeE.Int64: WriteInt64(Convert.ToInt64(value, CultureInfo.InvariantCulture)); break;
                case InternalPrimitiveTypeE.SByte: WriteSByte(Convert.ToSByte(value, CultureInfo.InvariantCulture)); break;
                case InternalPrimitiveTypeE.Single: WriteSingle(Convert.ToSingle(value, CultureInfo.InvariantCulture)); break;
                case InternalPrimitiveTypeE.UInt16: WriteUInt16(Convert.ToUInt16(value, CultureInfo.InvariantCulture)); break;
                case InternalPrimitiveTypeE.UInt32: WriteUInt32(Convert.ToUInt32(value, CultureInfo.InvariantCulture)); break;
                case InternalPrimitiveTypeE.UInt64: WriteUInt64(Convert.ToUInt64(value, CultureInfo.InvariantCulture)); break;
                case InternalPrimitiveTypeE.Decimal: WriteDecimal(Convert.ToDecimal(value, CultureInfo.InvariantCulture)); break;
                case InternalPrimitiveTypeE.TimeSpan: WriteTimeSpan((TimeSpan)value!); break;
                case InternalPrimitiveTypeE.DateTime: WriteDateTime((DateTime)value!); break;
                default: throw new SerializationException(SR.Format(SR.Serialization_TypeCode, code.ToString()));
            }
        }
 
        private sealed class ObjectMapInfo
        {
            internal readonly int _objectId;
            private readonly int _numMembers;
            private readonly string[] _memberNames;
            private readonly Type[] _memberTypes;
 
            internal ObjectMapInfo(int objectId, int numMembers, string[] memberNames, Type[] memberTypes)
            {
                _objectId = objectId;
                _numMembers = numMembers;
                _memberNames = memberNames;
                _memberTypes = memberTypes;
            }
 
            internal bool IsCompatible(int numMembers, string[] memberNames, Type[]? memberTypes)
            {
                if (_numMembers != numMembers)
                {
                    return false;
                }
 
                for (int i = 0; i < numMembers; i++)
                {
                    if (!(_memberNames[i].Equals(memberNames[i])))
                    {
                        return false;
                    }
 
                    if ((memberTypes != null) && (_memberTypes[i] != memberTypes[i]))
                    {
                        return false;
                    }
                }
 
                return true;
            }
        }
    }
}