|
// 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.Diagnostics.CodeAnalysis;
namespace System.Runtime.Serialization.Formatters.Binary
{
internal sealed class ObjectWriter
{
private const string ObjectWriterUnreferencedCodeMessage = "ObjectWriter requires unreferenced code";
private Queue<object>? _objectQueue;
private ObjectIDGenerator? _idGenerator;
private int _currentId;
private readonly ISurrogateSelector? _surrogates;
private readonly StreamingContext _context;
private BinaryFormatterWriter? _serWriter;
private readonly SerializationObjectManager _objectManager;
private long _topId;
private readonly InternalFE _formatterEnums;
private readonly SerializationBinder? _binder;
private SerObjectInfoInit? _serObjectInfoInit;
private IFormatterConverter? _formatterConverter;
#pragma warning disable 0649 // Field is never assigned to, and will always have its default value null
internal object[]? _crossAppDomainArray;
#pragma warning restore 0649
private object? _previousObj;
private long _previousId;
private Type? _previousType;
private InternalPrimitiveTypeE _previousCode = InternalPrimitiveTypeE.Invalid;
internal ObjectWriter(ISurrogateSelector? selector, StreamingContext context, InternalFE formatterEnums, SerializationBinder? binder)
{
_currentId = 1;
_surrogates = selector;
_context = context;
_binder = binder;
_formatterEnums = formatterEnums;
_objectManager = new SerializationObjectManager(context);
}
[RequiresUnreferencedCode(ObjectWriterUnreferencedCodeMessage)]
internal void Serialize(object graph, BinaryFormatterWriter serWriter)
{
ArgumentNullException.ThrowIfNull(graph);
ArgumentNullException.ThrowIfNull(serWriter);
_serWriter = serWriter;
serWriter.WriteBegin();
long headerId;
object? obj;
// allocations if methodCall or methodResponse and no graph
_idGenerator = new ObjectIDGenerator();
_objectQueue = new Queue<object>();
_formatterConverter = new FormatterConverter();
_serObjectInfoInit = new SerObjectInfoInit();
_topId = InternalGetId(graph, false, null, out _);
headerId = -1;
WriteSerializedStreamHeader(_topId, headerId);
_objectQueue.Enqueue(graph);
while ((obj = GetNext(out long objectId)) != null)
{
WriteObjectInfo? objectInfo;
// GetNext will return either an object or a WriteObjectInfo.
// A WriteObjectInfo is returned if this object was member of another object
if (obj is WriteObjectInfo)
{
objectInfo = (WriteObjectInfo)obj;
}
else
{
objectInfo = WriteObjectInfo.Serialize(obj, _surrogates, _context, _serObjectInfoInit, _formatterConverter, this, _binder);
objectInfo._assemId = GetAssemblyId(objectInfo);
}
objectInfo._objectId = objectId;
NameInfo typeNameInfo = TypeToNameInfo(objectInfo);
Write(objectInfo, typeNameInfo, typeNameInfo);
PutNameInfo(typeNameInfo);
objectInfo.ObjectEnd();
}
serWriter.WriteSerializationHeaderEnd();
serWriter.WriteEnd();
// Invoke OnSerialized Event
_objectManager.RaiseOnSerializedEvent();
}
internal SerializationObjectManager ObjectManager => _objectManager;
// Writes a given object to the stream.
[RequiresUnreferencedCode(ObjectWriterUnreferencedCodeMessage)]
private void Write(WriteObjectInfo objectInfo, NameInfo memberNameInfo, NameInfo typeNameInfo)
{
object? obj = objectInfo._obj;
if (obj == null)
{
throw new ArgumentNullException(nameof(objectInfo) + "." + nameof(objectInfo._obj));
}
Type? objType = objectInfo._objectType;
long objectId = objectInfo._objectId;
if (ReferenceEquals(objType, Converter.s_typeofString))
{
Debug.Assert(_serWriter != null);
// Top level String
memberNameInfo._objectId = objectId;
_serWriter.WriteObjectString((int)objectId, obj.ToString());
}
else
{
if (objectInfo._isArray)
{
WriteArray(objectInfo, memberNameInfo, null);
}
else
{
objectInfo.GetMemberInfo(out string[]? memberNames, out Type[]? memberTypes, out object?[]? memberData);
// Only Binary needs to transmit types for ISerializable because the binary formatter transmits the types in URT format.
// Soap transmits all types as strings, so it is up to the ISerializable object to convert the string back to its URT type
if (objectInfo._isSi || CheckTypeFormat(_formatterEnums._typeFormat, FormatterTypeStyle.TypesAlways))
{
memberNameInfo._transmitTypeOnObject = true;
memberNameInfo._isParentTypeOnObject = true;
typeNameInfo._transmitTypeOnObject = true;
typeNameInfo._isParentTypeOnObject = true;
}
Debug.Assert(memberNames != null && memberTypes != null && memberData != null);
var memberObjectInfos = new WriteObjectInfo[memberNames.Length];
// Get assembly information
// Binary Serializer, assembly names need to be
// written before objects are referenced.
// GetAssemId here will write out the
// assemblyStrings at the right Binary
// Serialization object boundary.
for (int i = 0; i < memberTypes.Length; i++)
{
Type type =
memberTypes[i] ?? (memberData[i] != null ? GetType(memberData[i]!) :
Converter.s_typeofObject);
InternalPrimitiveTypeE code = ToCode(type);
if ((code == InternalPrimitiveTypeE.Invalid) &&
(!ReferenceEquals(type, Converter.s_typeofString)))
{
Debug.Assert(_serObjectInfoInit != null && _formatterConverter != null);
if (memberData[i] != null)
{
memberObjectInfos[i] = WriteObjectInfo.Serialize(
memberData[i]!,
_surrogates,
_context,
_serObjectInfoInit,
_formatterConverter,
this,
_binder);
memberObjectInfos[i]._assemId = GetAssemblyId(memberObjectInfos[i]);
}
else
{
memberObjectInfos[i] = WriteObjectInfo.Serialize(
memberTypes[i],
_surrogates,
_context,
_serObjectInfoInit,
_formatterConverter,
_binder);
memberObjectInfos[i]._assemId = GetAssemblyId(memberObjectInfos[i]);
}
}
}
Write(objectInfo, memberNameInfo, typeNameInfo, memberNames, memberTypes, memberData, memberObjectInfos);
}
}
}
// Writes a given object to the stream.
[RequiresUnreferencedCode(ObjectWriterUnreferencedCodeMessage)]
private void Write(WriteObjectInfo objectInfo,
NameInfo? memberNameInfo,
NameInfo typeNameInfo,
string[] memberNames,
Type[] memberTypes,
object?[] memberData,
WriteObjectInfo[] memberObjectInfos)
{
int numItems = memberNames.Length;
Debug.Assert(_serWriter != null);
if (memberNameInfo != null)
{
memberNameInfo._objectId = objectInfo._objectId;
_serWriter.WriteObject(memberNameInfo, typeNameInfo, numItems, memberNames, memberTypes, memberObjectInfos);
}
else if (!ReferenceEquals(objectInfo._objectType, Converter.s_typeofString))
{
typeNameInfo._objectId = objectInfo._objectId;
_serWriter.WriteObject(typeNameInfo, null, numItems, memberNames, memberTypes, memberObjectInfos);
}
Debug.Assert(memberNameInfo != null);
if (memberNameInfo._isParentTypeOnObject)
{
memberNameInfo._transmitTypeOnObject = true;
memberNameInfo._isParentTypeOnObject = false;
}
else
{
memberNameInfo._transmitTypeOnObject = false;
}
// Write members
for (int i = 0; i < numItems; i++)
{
WriteMemberSetup(objectInfo, memberNameInfo, typeNameInfo, memberNames[i], memberTypes[i], memberData[i], memberObjectInfos[i]);
}
if (memberNameInfo != null)
{
memberNameInfo._objectId = objectInfo._objectId;
_serWriter.WriteObjectEnd(memberNameInfo, typeNameInfo);
}
else if (!ReferenceEquals(objectInfo._objectType, Converter.s_typeofString))
{
_serWriter.WriteObjectEnd(typeNameInfo, typeNameInfo);
}
}
[RequiresUnreferencedCode(ObjectWriterUnreferencedCodeMessage)]
private void WriteMemberSetup(WriteObjectInfo objectInfo,
NameInfo memberNameInfo,
NameInfo typeNameInfo,
string memberName,
Type memberType,
object? memberData,
WriteObjectInfo? memberObjectInfo)
{
NameInfo newMemberNameInfo = MemberToNameInfo(memberName); // newMemberNameInfo contains the member type
if (memberObjectInfo != null)
{
newMemberNameInfo._assemId = memberObjectInfo._assemId;
}
newMemberNameInfo._type = memberType;
// newTypeNameInfo contains the data type
NameInfo newTypeNameInfo;
if (memberObjectInfo == null)
{
newTypeNameInfo = TypeToNameInfo(memberType);
}
else
{
newTypeNameInfo = TypeToNameInfo(memberObjectInfo);
}
newMemberNameInfo._transmitTypeOnObject = memberNameInfo._transmitTypeOnObject;
newMemberNameInfo._isParentTypeOnObject = memberNameInfo._isParentTypeOnObject;
WriteMembers(newMemberNameInfo, newTypeNameInfo, memberData, objectInfo, typeNameInfo, memberObjectInfo);
PutNameInfo(newMemberNameInfo);
PutNameInfo(newTypeNameInfo);
}
// Writes the members of an object
[RequiresUnreferencedCode(ObjectWriterUnreferencedCodeMessage)]
private void WriteMembers(NameInfo memberNameInfo,
NameInfo memberTypeNameInfo,
object? memberData,
WriteObjectInfo objectInfo,
NameInfo typeNameInfo,
WriteObjectInfo? memberObjectInfo)
{
Type? memberType = memberNameInfo._type;
bool assignUniqueIdToValueType = false;
// Types are transmitted for a member as follows:
// The member is of type object
// The member object of type is ISerializable and
// Binary - Types always transmitted.
if (ReferenceEquals(memberType, Converter.s_typeofObject) || Nullable.GetUnderlyingType(memberType!) != null)
{
memberTypeNameInfo._transmitTypeOnMember = true;
memberNameInfo._transmitTypeOnMember = true;
}
if (CheckTypeFormat(_formatterEnums._typeFormat, FormatterTypeStyle.TypesAlways) || (objectInfo._isSi))
{
memberTypeNameInfo._transmitTypeOnObject = true;
memberNameInfo._transmitTypeOnObject = true;
memberNameInfo._isParentTypeOnObject = true;
}
if (CheckForNull(objectInfo, memberNameInfo, memberTypeNameInfo, memberData))
{
return;
}
object outObj = memberData!;
Type? outType = null;
// If member type does not equal data type, transmit type on object.
if (memberTypeNameInfo._primitiveTypeEnum == InternalPrimitiveTypeE.Invalid)
{
outType = GetType(outObj);
if (!ReferenceEquals(memberType, outType))
{
memberTypeNameInfo._transmitTypeOnMember = true;
memberNameInfo._transmitTypeOnMember = true;
}
}
if (ReferenceEquals(memberType, Converter.s_typeofObject))
{
assignUniqueIdToValueType = true;
memberType = GetType(memberData!);
if (memberObjectInfo == null)
{
TypeToNameInfo(memberType, memberTypeNameInfo);
}
else
{
TypeToNameInfo(memberObjectInfo, memberTypeNameInfo);
}
}
if (memberObjectInfo != null && memberObjectInfo._isArray)
{
// outObj is an array. It can never be a value type.
long arrayId = Schedule(outObj, false, null, memberObjectInfo);
if (arrayId > 0)
{
// Array as object
memberNameInfo._objectId = arrayId;
WriteObjectRef(memberNameInfo, arrayId);
}
else
{
Debug.Assert(_serWriter != null);
// Nested Array
_serWriter.WriteMemberNested(memberNameInfo);
memberObjectInfo._objectId = arrayId;
memberNameInfo._objectId = arrayId;
WriteArray(memberObjectInfo, memberNameInfo, memberObjectInfo);
objectInfo.ObjectEnd();
}
return;
}
if (!WriteKnownValueClass(memberNameInfo, memberTypeNameInfo, memberData!))
{
if (outType == null)
{
outType = GetType(outObj);
}
long memberObjectId = Schedule(outObj, assignUniqueIdToValueType, outType, memberObjectInfo);
if (memberObjectId < 0)
{
Debug.Assert(memberObjectInfo != null);
// Nested object
memberObjectInfo._objectId = memberObjectId;
NameInfo newTypeNameInfo = TypeToNameInfo(memberObjectInfo);
newTypeNameInfo._objectId = memberObjectId;
Write(memberObjectInfo, memberNameInfo, newTypeNameInfo);
PutNameInfo(newTypeNameInfo);
memberObjectInfo.ObjectEnd();
}
else
{
// Object reference
memberNameInfo._objectId = memberObjectId;
WriteObjectRef(memberNameInfo, memberObjectId);
}
}
}
// Writes out an array
[RequiresUnreferencedCode(ObjectWriterUnreferencedCodeMessage)]
private void WriteArray(WriteObjectInfo objectInfo, NameInfo? memberNameInfo, WriteObjectInfo? memberObjectInfo)
{
bool isAllocatedMemberNameInfo = false;
if (memberNameInfo == null)
{
memberNameInfo = TypeToNameInfo(objectInfo);
isAllocatedMemberNameInfo = true;
}
memberNameInfo._isArray = true;
long objectId = objectInfo._objectId;
memberNameInfo._objectId = objectInfo._objectId;
// Get array type
Array array = (Array)objectInfo._obj!;
//Type arrayType = array.GetType();
Type arrayType = objectInfo._objectType!;
// Get type of array element
Type arrayElemType = arrayType.GetElementType()!;
WriteObjectInfo? arrayElemObjectInfo = null;
if (!arrayElemType.IsPrimitive)
{
Debug.Assert(_serObjectInfoInit != null && _formatterConverter != null);
arrayElemObjectInfo = WriteObjectInfo.Serialize(arrayElemType, _surrogates, _context, _serObjectInfoInit, _formatterConverter, _binder);
arrayElemObjectInfo._assemId = GetAssemblyId(arrayElemObjectInfo);
}
NameInfo arrayElemTypeNameInfo = arrayElemObjectInfo == null ?
TypeToNameInfo(arrayElemType) :
TypeToNameInfo(arrayElemObjectInfo);
arrayElemTypeNameInfo._isArray = arrayElemTypeNameInfo._type!.IsArray;
NameInfo arrayNameInfo = memberNameInfo;
arrayNameInfo._objectId = objectId;
arrayNameInfo._isArray = true;
arrayElemTypeNameInfo._objectId = objectId;
arrayElemTypeNameInfo._transmitTypeOnMember = memberNameInfo._transmitTypeOnMember;
arrayElemTypeNameInfo._transmitTypeOnObject = memberNameInfo._transmitTypeOnObject;
arrayElemTypeNameInfo._isParentTypeOnObject = memberNameInfo._isParentTypeOnObject;
// Get rank and length information
int rank = array.Rank;
int[] lengthA = new int[rank];
int[] lowerBoundA = new int[rank];
int[] upperBoundA = new int[rank];
for (int i = 0; i < rank; i++)
{
lengthA[i] = array.GetLength(i);
lowerBoundA[i] = array.GetLowerBound(i);
upperBoundA[i] = array.GetUpperBound(i);
}
InternalArrayTypeE arrayEnum;
if (arrayElemTypeNameInfo._isArray)
{
arrayEnum = rank == 1 ? InternalArrayTypeE.Jagged : InternalArrayTypeE.Rectangular;
}
else if (rank == 1)
{
arrayEnum = InternalArrayTypeE.Single;
}
else
{
arrayEnum = InternalArrayTypeE.Rectangular;
}
arrayElemTypeNameInfo._arrayEnum = arrayEnum;
Debug.Assert(_serWriter != null);
// Byte array
if ((ReferenceEquals(arrayElemType, Converter.s_typeofByte)) && (rank == 1) && (lowerBoundA[0] == 0))
{
_serWriter.WriteObjectByteArray(memberNameInfo, arrayNameInfo, arrayElemObjectInfo, arrayElemTypeNameInfo, lengthA[0], lowerBoundA[0], (byte[])array);
return;
}
if (ReferenceEquals(arrayElemType, Converter.s_typeofObject) || Nullable.GetUnderlyingType(arrayElemType) != null)
{
memberNameInfo._transmitTypeOnMember = true;
arrayElemTypeNameInfo._transmitTypeOnMember = true;
}
if (CheckTypeFormat(_formatterEnums._typeFormat, FormatterTypeStyle.TypesAlways))
{
memberNameInfo._transmitTypeOnObject = true;
arrayElemTypeNameInfo._transmitTypeOnObject = true;
}
if (arrayEnum == InternalArrayTypeE.Single)
{
// Single Dimensional array
// BinaryFormatter array of primitive types is written out in the WriteSingleArray statement
// as a byte buffer
_serWriter.WriteSingleArray(memberNameInfo, arrayNameInfo, arrayElemObjectInfo, arrayElemTypeNameInfo, lengthA[0], lowerBoundA[0], array);
if (!(Converter.IsWriteAsByteArray(arrayElemTypeNameInfo._primitiveTypeEnum) && (lowerBoundA[0] == 0)))
{
object[]? objectA = null;
if (!arrayElemType.IsValueType)
{
// Non-primitive type array
objectA = (object[])array;
}
int upperBound = upperBoundA[0] + 1;
for (int i = lowerBoundA[0]; i < upperBound; i++)
{
if (objectA == null)
{
WriteArrayMember(objectInfo, arrayElemTypeNameInfo, array.GetValue(i));
}
else
{
WriteArrayMember(objectInfo, arrayElemTypeNameInfo, objectA[i]);
}
}
_serWriter.WriteItemEnd();
}
}
else if (arrayEnum == InternalArrayTypeE.Jagged)
{
// Jagged Array
arrayNameInfo._objectId = objectId;
_serWriter.WriteJaggedArray(memberNameInfo, arrayNameInfo, arrayElemObjectInfo, arrayElemTypeNameInfo, lengthA[0], lowerBoundA[0]);
var objectA = (Array)array;
for (int i = lowerBoundA[0]; i < upperBoundA[0] + 1; i++)
{
WriteArrayMember(objectInfo, arrayElemTypeNameInfo, objectA.GetValue(i));
}
_serWriter.WriteItemEnd();
}
else
{
// Rectangle Array
// Get the length for all the ranks
arrayNameInfo._objectId = objectId;
_serWriter.WriteRectangleArray(memberNameInfo, arrayNameInfo, arrayElemObjectInfo, arrayElemTypeNameInfo, rank, lengthA, lowerBoundA);
// Check for a length of zero
bool bzero = false;
for (int i = 0; i < rank; i++)
{
if (lengthA[i] == 0)
{
bzero = true;
break;
}
}
if (!bzero)
{
WriteRectangle(objectInfo, rank, lengthA, array, arrayElemTypeNameInfo, lowerBoundA);
}
_serWriter.WriteItemEnd();
}
_serWriter.WriteObjectEnd(memberNameInfo, arrayNameInfo);
PutNameInfo(arrayElemTypeNameInfo);
if (isAllocatedMemberNameInfo)
{
PutNameInfo(memberNameInfo);
}
}
// Writes out an array element
[RequiresUnreferencedCode(ObjectWriterUnreferencedCodeMessage)]
private void WriteArrayMember(WriteObjectInfo objectInfo, NameInfo arrayElemTypeNameInfo, object? data)
{
arrayElemTypeNameInfo._isArrayItem = true;
if (CheckForNull(objectInfo, arrayElemTypeNameInfo, arrayElemTypeNameInfo, data))
{
return;
}
NameInfo? actualTypeInfo;
Type? dataType = null;
bool isObjectOnMember = false;
if (arrayElemTypeNameInfo._transmitTypeOnMember)
{
isObjectOnMember = true;
}
if (!isObjectOnMember && !arrayElemTypeNameInfo.IsSealed)
{
dataType = GetType(data!);
if (!ReferenceEquals(arrayElemTypeNameInfo._type, dataType))
{
isObjectOnMember = true;
}
}
if (isObjectOnMember)
{
// Object array, need type of member
if (dataType == null)
{
dataType = GetType(data!);
}
actualTypeInfo = TypeToNameInfo(dataType);
actualTypeInfo._transmitTypeOnMember = true;
actualTypeInfo._objectId = arrayElemTypeNameInfo._objectId;
actualTypeInfo._assemId = arrayElemTypeNameInfo._assemId;
actualTypeInfo._isArrayItem = true;
}
else
{
actualTypeInfo = arrayElemTypeNameInfo;
actualTypeInfo._isArrayItem = true;
}
if (!WriteKnownValueClass(arrayElemTypeNameInfo, actualTypeInfo, data!))
{
object obj = data!;
bool assignUniqueIdForValueTypes = false;
if (ReferenceEquals(arrayElemTypeNameInfo._type, Converter.s_typeofObject))
{
assignUniqueIdForValueTypes = true;
}
long arrayId = Schedule(obj, assignUniqueIdForValueTypes, actualTypeInfo._type);
arrayElemTypeNameInfo._objectId = arrayId;
actualTypeInfo._objectId = arrayId;
if (arrayId < 1)
{
Debug.Assert(_serObjectInfoInit != null && _formatterConverter != null);
WriteObjectInfo newObjectInfo = WriteObjectInfo.Serialize(obj, _surrogates, _context, _serObjectInfoInit, _formatterConverter, this, _binder);
newObjectInfo._objectId = arrayId;
newObjectInfo._assemId = !ReferenceEquals(arrayElemTypeNameInfo._type, Converter.s_typeofObject) && Nullable.GetUnderlyingType(arrayElemTypeNameInfo._type!) == null ?
actualTypeInfo._assemId :
GetAssemblyId(newObjectInfo);
NameInfo typeNameInfo = TypeToNameInfo(newObjectInfo);
typeNameInfo._objectId = arrayId;
newObjectInfo._objectId = arrayId;
Write(newObjectInfo, actualTypeInfo, typeNameInfo);
newObjectInfo.ObjectEnd();
}
else
{
Debug.Assert(_serWriter != null);
_serWriter.WriteItemObjectRef(arrayElemTypeNameInfo, (int)arrayId);
}
}
if (arrayElemTypeNameInfo._transmitTypeOnMember)
{
PutNameInfo(actualTypeInfo);
}
}
// Iterates over a Rectangle array, for each element of the array invokes WriteArrayMember
[RequiresUnreferencedCode(ObjectWriterUnreferencedCodeMessage)]
private void WriteRectangle(WriteObjectInfo objectInfo, int rank, int[] maxA, Array array, NameInfo arrayElemNameTypeInfo, int[]? lowerBoundA)
{
int[] currentA = new int[rank];
int[]? indexMap = null;
bool isLowerBound = false;
if (lowerBoundA != null)
{
for (int i = 0; i < rank; i++)
{
if (lowerBoundA[i] != 0)
{
isLowerBound = true;
}
}
}
if (isLowerBound)
{
indexMap = new int[rank];
}
bool isLoop = true;
while (isLoop)
{
isLoop = false;
if (isLowerBound)
{
for (int i = 0; i < rank; i++)
{
indexMap![i] = currentA[i] + lowerBoundA![i];
}
WriteArrayMember(objectInfo, arrayElemNameTypeInfo, array.GetValue(indexMap!));
}
else
{
WriteArrayMember(objectInfo, arrayElemNameTypeInfo, array.GetValue(currentA));
}
for (int irank = rank - 1; irank > -1; irank--)
{
// Find the current or lower dimension which can be incremented.
if (currentA[irank] < maxA[irank] - 1)
{
// The current dimension is at maximum. Increase the next lower dimension by 1
currentA[irank]++;
if (irank < rank - 1)
{
// The current dimension and higher dimensions are zeroed.
for (int i = irank + 1; i < rank; i++)
{
currentA[i] = 0;
}
}
isLoop = true;
break;
}
}
}
}
// This gives back the next object to be serialized. Objects
// are returned in a FIFO order based on how they were passed
// to Schedule. The id of the object is put into the objID parameter
// and the Object itself is returned from the function.
private object? GetNext(out long objID)
{
bool isNew;
//The Queue is empty here. We'll throw if we try to dequeue the empty queue.
if (_objectQueue!.Count == 0)
{
objID = 0;
return null;
}
object obj = _objectQueue.Dequeue();
// A WriteObjectInfo is queued if this object was a member of another object
object? realObj = obj is WriteObjectInfo ? ((WriteObjectInfo)obj)._obj : obj;
Debug.Assert(realObj != null);
objID = _idGenerator!.HasId(realObj, out isNew);
if (isNew)
{
throw new SerializationException(SR.Format(SR.Serialization_ObjNoID, realObj));
}
return obj;
}
// If the type is a value type, we dont attempt to generate a unique id, unless its a boxed entity
// (in which case, there might be 2 references to the same boxed obj. in a graph.)
// "assignUniqueIdToValueType" is true, if the field type holding reference to "obj" is Object.
private long InternalGetId(object obj, bool assignUniqueIdToValueType, Type? type, out bool isNew)
{
if (obj == _previousObj)
{
// good for benchmarks
isNew = false;
return _previousId;
}
Debug.Assert(_idGenerator != null);
_idGenerator._currentCount = _currentId;
if (type != null && type.IsValueType)
{
if (!assignUniqueIdToValueType)
{
isNew = false;
return -1 * _currentId++;
}
}
_currentId++;
long retId = _idGenerator.GetId(obj, out isNew);
_previousObj = obj;
_previousId = retId;
return retId;
}
// Schedules an object for later serialization if it hasn't already been scheduled.
// We get an ID for obj and put it on the queue for later serialization
// if this is a new object id.
private long Schedule(object obj, bool assignUniqueIdToValueType, Type? type) =>
Schedule(obj, assignUniqueIdToValueType, type, null);
private long Schedule(object obj, bool assignUniqueIdToValueType, Type? type, WriteObjectInfo? objectInfo)
{
long id = 0;
if (obj != null)
{
bool isNew;
id = InternalGetId(obj, assignUniqueIdToValueType, type, out isNew);
if (isNew && id > 0)
{
Debug.Assert(_objectQueue != null);
_objectQueue.Enqueue(objectInfo ?? obj);
}
}
return id;
}
// Determines if a type is a primitive type, if it is it is written
private bool WriteKnownValueClass(NameInfo memberNameInfo, NameInfo typeNameInfo, object data)
{
if (ReferenceEquals(typeNameInfo._type, Converter.s_typeofString))
{
WriteString(memberNameInfo, typeNameInfo, data);
}
else
{
if (typeNameInfo._primitiveTypeEnum == InternalPrimitiveTypeE.Invalid)
{
return false;
}
else
{
Debug.Assert(_serWriter != null);
if (typeNameInfo._isArray) // null if an array
{
_serWriter.WriteItem(memberNameInfo, typeNameInfo, data);
}
else
{
_serWriter.WriteMember(memberNameInfo, typeNameInfo, data);
}
}
}
return true;
}
// Writes an object reference to the stream.
private void WriteObjectRef(NameInfo nameInfo, long objectId) =>
_serWriter!.WriteMemberObjectRef(nameInfo, (int)objectId);
// Writes a string into the XML stream
private void WriteString(NameInfo memberNameInfo, NameInfo typeNameInfo, object stringObject)
{
bool isFirstTime = true;
long stringId = -1;
if (!CheckTypeFormat(_formatterEnums._typeFormat, FormatterTypeStyle.XsdString))
{
stringId = InternalGetId(stringObject, false, null, out isFirstTime);
}
typeNameInfo._objectId = stringId;
if ((isFirstTime) || (stringId < 0))
{
Debug.Assert(_serWriter != null);
_serWriter.WriteMemberString(memberNameInfo, typeNameInfo, (string)stringObject);
}
else
{
WriteObjectRef(memberNameInfo, stringId);
}
}
// Writes a null member into the stream
private bool CheckForNull(WriteObjectInfo objectInfo, NameInfo memberNameInfo, NameInfo typeNameInfo, object? data)
{
bool isNull = data == null;
// Optimization, Null members are only written for Binary
if ((isNull) && (((_formatterEnums._serializerTypeEnum == InternalSerializerTypeE.Binary)) ||
memberNameInfo._isArrayItem ||
memberNameInfo._transmitTypeOnObject ||
memberNameInfo._transmitTypeOnMember ||
objectInfo._isSi ||
(CheckTypeFormat(_formatterEnums._typeFormat, FormatterTypeStyle.TypesAlways))))
{
Debug.Assert(_serWriter != null);
if (typeNameInfo._isArrayItem)
{
if (typeNameInfo._arrayEnum == InternalArrayTypeE.Single)
{
_serWriter.WriteDelayedNullItem();
}
else
{
_serWriter.WriteNullItem(memberNameInfo, typeNameInfo);
}
}
else
{
_serWriter.WriteNullMember(memberNameInfo, typeNameInfo);
}
}
return isNull;
}
// Writes the SerializedStreamHeader
private void WriteSerializedStreamHeader(long topId, long headerId) =>
_serWriter!.WriteSerializationHeader((int)topId, (int)headerId, 1, 0);
// Transforms a type to the serialized string form. URT Primitive types are converted to XMLData Types
private NameInfo TypeToNameInfo(Type? type, WriteObjectInfo? objectInfo, InternalPrimitiveTypeE code, NameInfo? nameInfo)
{
if (nameInfo == null)
{
nameInfo = GetNameInfo();
}
else
{
nameInfo.Init();
}
if (code == InternalPrimitiveTypeE.Invalid)
{
if (objectInfo != null)
{
nameInfo.NIname = objectInfo.GetTypeFullName();
nameInfo._assemId = objectInfo._assemId;
}
}
nameInfo._primitiveTypeEnum = code;
nameInfo._type = type;
return nameInfo;
}
private NameInfo TypeToNameInfo(Type type) =>
TypeToNameInfo(type, null, ToCode(type), null);
private NameInfo TypeToNameInfo(WriteObjectInfo objectInfo) =>
TypeToNameInfo(objectInfo._objectType, objectInfo, ToCode(objectInfo._objectType), null);
private NameInfo TypeToNameInfo(WriteObjectInfo objectInfo, NameInfo nameInfo) =>
TypeToNameInfo(objectInfo._objectType, objectInfo, ToCode(objectInfo._objectType), nameInfo);
private void TypeToNameInfo(Type type, NameInfo nameInfo) =>
TypeToNameInfo(type, null, ToCode(type), nameInfo);
private NameInfo MemberToNameInfo(string name)
{
NameInfo memberNameInfo = GetNameInfo();
memberNameInfo.NIname = name;
return memberNameInfo;
}
internal InternalPrimitiveTypeE ToCode(Type? type)
{
if (ReferenceEquals(_previousType, type))
{
return _previousCode;
}
else
{
InternalPrimitiveTypeE code = Converter.ToCode(type);
if (code != InternalPrimitiveTypeE.Invalid)
{
_previousType = type;
_previousCode = code;
}
return code;
}
}
private Dictionary<string, long>? _assemblyToIdTable;
private long GetAssemblyId(WriteObjectInfo objectInfo)
{
//use objectInfo to get assembly string with new criteria
_assemblyToIdTable ??= new Dictionary<string, long>();
long assemId;
string assemblyString = objectInfo.GetAssemblyString();
string serializedAssemblyString = assemblyString;
if (assemblyString.Length == 0)
{
assemId = 0;
}
else if (assemblyString.Equals(Converter.s_urtAssemblyString) || assemblyString.Equals(Converter.s_urtAlternativeAssemblyString))
{
// Urt type is an assemId of 0. No assemblyString needs
// to be sent
assemId = 0;
}
else
{
// Assembly needs to be sent
// Need to prefix assembly string to separate the string names from the
// assemblyName string names. That is a string can have the same value
// as an assemblyNameString, but it is serialized differently
bool isNew;
if (_assemblyToIdTable.TryGetValue(assemblyString, out assemId))
{
isNew = false;
}
else
{
assemId = InternalGetId("___AssemblyString___" + assemblyString, false, null, out isNew);
_assemblyToIdTable[assemblyString] = assemId;
}
Debug.Assert(_serWriter != null);
_serWriter.WriteAssembly(objectInfo._objectType, serializedAssemblyString, (int)assemId, isNew);
}
return assemId;
}
private Type GetType(object obj) => obj.GetType();
private readonly SerStack _niPool = new SerStack("NameInfo Pool");
private NameInfo GetNameInfo()
{
NameInfo nameInfo;
if (!_niPool.IsEmpty())
{
nameInfo = (NameInfo)_niPool.Pop()!;
nameInfo.Init();
}
else
{
nameInfo = new NameInfo();
}
return nameInfo;
}
private bool CheckTypeFormat(FormatterTypeStyle test, FormatterTypeStyle want) => (test & want) == want;
private void PutNameInfo(NameInfo nameInfo) => _niPool.Push(nameInfo);
}
}
|