Markerless serialization for value types (UseMetadata=false)
Introduced markerless serialization/deserialization for non-nullable value type properties when UseMetadata is false, eliminating type marker bytes for int, long, double, etc. Added ExpectedTypeCode to property accessors/setters to enable this optimization. Refactored property loops in serializer/deserializer for performance and clarity. Default UseMetadata is now false. Improves speed and reduces stream size for common value types while maintaining compatibility for complex types.
This commit is contained in:
parent
b38fd480d8
commit
97b7813633
|
|
@ -77,108 +77,196 @@ public static partial class AcBinaryDeserializer
|
|||
// Non-UseMetadata: properties.Length a target property-k száma (source == target)
|
||||
var propCount = cacheMap?.Length ?? properties.Length;
|
||||
|
||||
for (int i = 0; i < propCount; i++)
|
||||
if (!context.HasMetadata)
|
||||
{
|
||||
var propInfo = cacheMap != null ? cacheMap[i] : properties[i];
|
||||
|
||||
var peekCode = context.PeekByte();
|
||||
|
||||
// Nincs megfelelő target property → skip
|
||||
if (propInfo == null)
|
||||
// Markerless loop: properties with ExpectedTypeCode read raw values directly.
|
||||
// Properties without ExpectedTypeCode (bool, enum, string, object) use standard marker path.
|
||||
for (int i = 0; i < propCount; i++)
|
||||
{
|
||||
SkipValue(ref context, metadata);
|
||||
continue;
|
||||
}
|
||||
var propInfo = properties[i];
|
||||
|
||||
// Skip marker - property has default/null value
|
||||
if (peekCode == BinaryTypeCode.PropertySkip)
|
||||
{
|
||||
context.ReadByte(); // consume Skip marker
|
||||
|
||||
// Populate mode: overwrite with default (existing object may have non-default values)
|
||||
// Deserialize mode: skip write (new object already has defaults from CreateInstance)
|
||||
if (!skipDefaultWrite)
|
||||
if (propInfo.ExpectedTypeCode.HasValue)
|
||||
{
|
||||
SetPropertyToDefault(target, propInfo);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Null values - always set
|
||||
if (peekCode == BinaryTypeCode.Null)
|
||||
{
|
||||
context.ReadByte(); // consume Null marker
|
||||
propInfo.SetValue(target, null);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle collections
|
||||
if (peekCode == BinaryTypeCode.Array && propInfo.IsCollection)
|
||||
{
|
||||
var existingCollection = propInfo.GetValue(target);
|
||||
if (existingCollection is IList existingList)
|
||||
{
|
||||
context.ReadByte(); // consume Array marker
|
||||
|
||||
// Merge mode with IId collection: use merge logic
|
||||
if (isMergeMode && propInfo.IsIIdCollection)
|
||||
{
|
||||
MergeIIdCollection(ref context, existingList, propInfo, nextDepth);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Normal populate: replace collection contents
|
||||
PopulateListOptimized(ref context, existingList, propInfo, nextDepth);
|
||||
}
|
||||
ReadAndSetMarkerlessValue(ref context, target, propInfo);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle nested complex objects - reuse existing if available
|
||||
if ((peekCode == BinaryTypeCode.Object || peekCode == BinaryTypeCode.ObjectWithMetadata) && propInfo.IsComplexType)
|
||||
// Non-markerless properties: standard marker-based read
|
||||
PopulatePropertyWithMarker(ref context, target, propInfo, metadata, nextDepth, isMergeMode, skipDefaultWrite, i, depth);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// UseMetadata=true loop — UNCHANGED, zero extra overhead
|
||||
for (int i = 0; i < propCount; i++)
|
||||
{
|
||||
var existingObj = propInfo.GetValue(target);
|
||||
if (existingObj != null)
|
||||
var propInfo = cacheMap != null ? cacheMap[i] : properties[i];
|
||||
|
||||
PopulatePropertyWithMarker(ref context, target, propInfo, metadata, nextDepth, isMergeMode, skipDefaultWrite, i, depth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Standard marker-based property read. Extracted to avoid duplicating logic in both loops.
|
||||
/// </summary>
|
||||
private static void PopulatePropertyWithMarker(
|
||||
ref BinaryDeserializationContext context,
|
||||
object target,
|
||||
BinaryPropertySetterBase? propInfo,
|
||||
BinaryDeserializeTypeMetadata metadata,
|
||||
int nextDepth,
|
||||
bool isMergeMode,
|
||||
bool skipDefaultWrite,
|
||||
int propertyIndex,
|
||||
int depth)
|
||||
{
|
||||
var peekCode = context.PeekByte();
|
||||
|
||||
// Nincs megfelelő target property → skip
|
||||
if (propInfo == null)
|
||||
{
|
||||
SkipValue(ref context, metadata);
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip marker - property has default/null value
|
||||
if (peekCode == BinaryTypeCode.PropertySkip)
|
||||
{
|
||||
context.ReadByte(); // consume Skip marker
|
||||
|
||||
// Populate mode: overwrite with default (existing object may have non-default values)
|
||||
// Deserialize mode: skip write (new object already has defaults from CreateInstance)
|
||||
if (!skipDefaultWrite)
|
||||
{
|
||||
SetPropertyToDefault(target, propInfo);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Null values - always set
|
||||
if (peekCode == BinaryTypeCode.Null)
|
||||
{
|
||||
context.ReadByte(); // consume Null marker
|
||||
propInfo.SetValue(target, null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle collections
|
||||
if (peekCode == BinaryTypeCode.Array && propInfo.IsCollection)
|
||||
{
|
||||
var existingCollection = propInfo.GetValue(target);
|
||||
if (existingCollection is IList existingList)
|
||||
{
|
||||
context.ReadByte(); // consume Array marker
|
||||
|
||||
// Merge mode with IId collection: use merge logic
|
||||
if (isMergeMode && propInfo.IsIIdCollection)
|
||||
{
|
||||
// ReadValue kezeli mindkét markert
|
||||
var nestedValue = ReadValue(ref context, propInfo.PropertyType, nextDepth);
|
||||
if (nestedValue != null)
|
||||
{
|
||||
var nestedMeta = context.ContextClass.GetWrapper(propInfo.PropertyType).Metadata;
|
||||
CopyProperties(nestedValue, existingObj, nestedMeta);
|
||||
}
|
||||
continue;
|
||||
MergeIIdCollection(ref context, existingList, propInfo, nextDepth);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Normal populate: replace collection contents
|
||||
PopulateListOptimized(ref context, existingList, propInfo, nextDepth);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Default: read value and set (for primitives, strings, new objects)
|
||||
var positionBeforeRead = context.Position;
|
||||
try
|
||||
// Handle nested complex objects - reuse existing if available
|
||||
if ((peekCode == BinaryTypeCode.Object || peekCode == BinaryTypeCode.ObjectWithMetadata) && propInfo.IsComplexType)
|
||||
{
|
||||
var existingObj = propInfo.GetValue(target);
|
||||
if (existingObj != null)
|
||||
{
|
||||
// Use typed setters for primitives and strings to avoid ReadValue dispatch
|
||||
if (propInfo.AccessorType != PropertyAccessorType.Object &&
|
||||
TryReadAndSetTypedValue(ref context, target, propInfo, peekCode))
|
||||
continue;
|
||||
// ReadValue kezeli mindkét markert
|
||||
var nestedValue = ReadValue(ref context, propInfo.PropertyType, nextDepth);
|
||||
if (nestedValue != null)
|
||||
{
|
||||
var nestedMeta = context.ContextClass.GetWrapper(propInfo.PropertyType).Metadata;
|
||||
CopyProperties(nestedValue, existingObj, nestedMeta);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var value = ReadValue(ref context, propInfo.PropertyType, nextDepth);
|
||||
propInfo.SetValue(target, value);
|
||||
}
|
||||
catch (InvalidCastException ex)
|
||||
{
|
||||
var targetType = target.GetType();
|
||||
throw new AcBinaryDeserializationException(
|
||||
$"Type mismatch for property '{propInfo.Name}' (index {i}) on '{targetType.Name}'. " +
|
||||
$"Expected type: '{propInfo.PropertyType.FullName}'. " +
|
||||
$"PeekCode before read: {peekCode} (0x{peekCode:X2}). " +
|
||||
$"Position before read: {positionBeforeRead}, current: {context.Position}. " +
|
||||
$"Depth: {depth}. " +
|
||||
$"Target type: {targetType.FullName}, Assembly: {targetType.Assembly.GetName().Name} v{targetType.Assembly.GetName().Version}. " +
|
||||
$"All target properties: [{string.Join(", ", metadata.PropertiesArray.Select(p => $"{p.Name}:{p.PropertyType.Name}"))}]. " +
|
||||
$"Error: {ex.Message}",
|
||||
positionBeforeRead,
|
||||
propInfo.PropertyType,
|
||||
ex);
|
||||
}
|
||||
// Default: read value and set (for primitives, strings, new objects)
|
||||
var positionBeforeRead = context.Position;
|
||||
try
|
||||
{
|
||||
// Use typed setters for primitives and strings to avoid ReadValue dispatch
|
||||
if (propInfo.AccessorType != PropertyAccessorType.Object &&
|
||||
TryReadAndSetTypedValue(ref context, target, propInfo, peekCode))
|
||||
return;
|
||||
|
||||
var value = ReadValue(ref context, propInfo.PropertyType, nextDepth);
|
||||
propInfo.SetValue(target, value);
|
||||
}
|
||||
catch (InvalidCastException ex)
|
||||
{
|
||||
var targetType = target.GetType();
|
||||
throw new AcBinaryDeserializationException(
|
||||
$"Type mismatch for property '{propInfo.Name}' (index {propertyIndex}) on '{targetType.Name}'. " +
|
||||
$"Expected type: '{propInfo.PropertyType.FullName}'. " +
|
||||
$"PeekCode before read: {peekCode} (0x{peekCode:X2}). " +
|
||||
$"Position before read: {positionBeforeRead}, current: {context.Position}. " +
|
||||
$"Depth: {depth}. " +
|
||||
$"Target type: {targetType.FullName}, Assembly: {targetType.Assembly.GetName().Name} v{targetType.Assembly.GetName().Version}. " +
|
||||
$"All target properties: [{string.Join(", ", metadata.PropertiesArray.Select(p => $"{p.Name}:{p.PropertyType.Name}"))}]. " +
|
||||
$"Error: {ex.Message}",
|
||||
positionBeforeRead,
|
||||
propInfo.PropertyType,
|
||||
ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a raw value without type marker from stream (markerless mode, UseMetadata=false).
|
||||
/// The property's type is known from metadata — no type code in the stream.
|
||||
/// Only called for non-nullable value types with ExpectedTypeCode set.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ReadAndSetMarkerlessValue(ref BinaryDeserializationContext context, object target, BinaryPropertySetterBase propInfo)
|
||||
{
|
||||
switch (propInfo.AccessorType)
|
||||
{
|
||||
case PropertyAccessorType.Int32:
|
||||
propInfo.SetInt32(target, context.ReadVarInt());
|
||||
return;
|
||||
case PropertyAccessorType.Int64:
|
||||
propInfo.SetInt64(target, context.ReadVarLong());
|
||||
return;
|
||||
case PropertyAccessorType.Double:
|
||||
propInfo.SetDouble(target, context.ReadDoubleUnsafe());
|
||||
return;
|
||||
case PropertyAccessorType.Single:
|
||||
propInfo.SetSingle(target, context.ReadSingleUnsafe());
|
||||
return;
|
||||
case PropertyAccessorType.Decimal:
|
||||
propInfo.SetDecimal(target, context.ReadDecimalUnsafe());
|
||||
return;
|
||||
case PropertyAccessorType.DateTime:
|
||||
propInfo.SetDateTime(target, context.ReadDateTimeUnsafe());
|
||||
return;
|
||||
case PropertyAccessorType.Guid:
|
||||
propInfo.SetGuid(target, context.ReadGuidUnsafe());
|
||||
return;
|
||||
case PropertyAccessorType.Byte:
|
||||
propInfo.SetByte(target, context.ReadByte());
|
||||
return;
|
||||
case PropertyAccessorType.Int16:
|
||||
propInfo.SetInt16(target, context.ReadInt16Unsafe());
|
||||
return;
|
||||
case PropertyAccessorType.UInt16:
|
||||
propInfo.SetUInt16(target, context.ReadUInt16Unsafe());
|
||||
return;
|
||||
case PropertyAccessorType.UInt32:
|
||||
propInfo.SetUInt32(target, context.ReadVarUInt());
|
||||
return;
|
||||
case PropertyAccessorType.UInt64:
|
||||
propInfo.SetUInt64(target, context.ReadVarULong());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -944,27 +944,46 @@ public static partial class AcBinarySerializer
|
|||
var nextDepth = depth + 1;
|
||||
var properties = metadata.Properties;
|
||||
var propCount = properties.Length;
|
||||
|
||||
// Single-pass serialization with SKIP markers
|
||||
// - No property count needed (fixed property order)
|
||||
// - No property indices needed (sequential order)
|
||||
// - Single getter call per property
|
||||
// - Write value OR skip marker in one operation
|
||||
var hasPropertyFilter = context.HasPropertyFilter;
|
||||
|
||||
for (var i = 0; i < propCount; i++)
|
||||
if (!context.UseMetadata)
|
||||
{
|
||||
var prop = properties[i];
|
||||
|
||||
// Skip if filter says no - write skip marker
|
||||
if (hasPropertyFilter && !context.ShouldSerializeProperty(value, prop))
|
||||
// Markerless loop: no extra branching per property for the common case.
|
||||
// Properties with ExpectedTypeCode write raw values (no type marker, no skip).
|
||||
// Properties without ExpectedTypeCode (bool, enum, string, object) use the standard path.
|
||||
for (var i = 0; i < propCount; i++)
|
||||
{
|
||||
context.WriteByte(BinaryTypeCode.PropertySkip);
|
||||
continue;
|
||||
var prop = properties[i];
|
||||
|
||||
if (prop.ExpectedTypeCode.HasValue)
|
||||
{
|
||||
WritePropertyMarkerless(value, prop, context);
|
||||
}
|
||||
else if (hasPropertyFilter && !context.ShouldSerializeProperty(value, prop))
|
||||
{
|
||||
context.WriteByte(BinaryTypeCode.PropertySkip);
|
||||
}
|
||||
else
|
||||
{
|
||||
WritePropertyOrSkip(value, prop, context, nextDepth);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// UseMetadata=true loop — UNCHANGED, zero extra overhead
|
||||
for (var i = 0; i < propCount; i++)
|
||||
{
|
||||
var prop = properties[i];
|
||||
|
||||
if (hasPropertyFilter && !context.ShouldSerializeProperty(value, prop))
|
||||
{
|
||||
context.WriteByte(BinaryTypeCode.PropertySkip);
|
||||
continue;
|
||||
}
|
||||
|
||||
WritePropertyOrSkip(value, prop, context, nextDepth);
|
||||
}
|
||||
|
||||
// Write property value OR skip marker (single operation, single getter call)
|
||||
WritePropertyOrSkip(value, prop, context, nextDepth);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1283,6 +1302,55 @@ public static partial class AcBinarySerializer
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a property value without type marker byte (markerless mode, UseMetadata=false).
|
||||
/// All values are written including defaults — no PropertySkip markers.
|
||||
/// Only called for non-nullable value types with ExpectedTypeCode set.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void WritePropertyMarkerless(object obj, BinaryPropertyAccessor prop, BinarySerializationContext context)
|
||||
{
|
||||
switch (prop.AccessorType)
|
||||
{
|
||||
case PropertyAccessorType.Int32:
|
||||
context.WriteVarInt(prop.GetInt32(obj));
|
||||
return;
|
||||
case PropertyAccessorType.Int64:
|
||||
context.WriteVarLong(prop.GetInt64(obj));
|
||||
return;
|
||||
case PropertyAccessorType.Double:
|
||||
context.WriteRaw(prop.GetDouble(obj));
|
||||
return;
|
||||
case PropertyAccessorType.Single:
|
||||
context.WriteRaw(prop.GetSingle(obj));
|
||||
return;
|
||||
case PropertyAccessorType.Decimal:
|
||||
context.WriteDecimalBits(prop.GetDecimal(obj));
|
||||
return;
|
||||
case PropertyAccessorType.DateTime:
|
||||
context.WriteDateTimeBits(prop.GetDateTime(obj));
|
||||
return;
|
||||
case PropertyAccessorType.Guid:
|
||||
context.WriteGuidBits(prop.GetGuid(obj));
|
||||
return;
|
||||
case PropertyAccessorType.Byte:
|
||||
context.WriteByte(prop.GetByte(obj));
|
||||
return;
|
||||
case PropertyAccessorType.Int16:
|
||||
context.WriteRaw(prop.GetInt16(obj));
|
||||
return;
|
||||
case PropertyAccessorType.UInt16:
|
||||
context.WriteRaw(prop.GetUInt16(obj));
|
||||
return;
|
||||
case PropertyAccessorType.UInt32:
|
||||
context.WriteVarUInt(prop.GetUInt32(obj));
|
||||
return;
|
||||
case PropertyAccessorType.UInt64:
|
||||
context.WriteVarULong(prop.GetUInt64(obj));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Specialized Array Writers
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ public sealed class AcBinarySerializerOptions : AcSerializerOptions
|
|||
/// allowing the deserializer to match properties by name between different types.
|
||||
/// Default: false (no overhead)
|
||||
/// </summary>
|
||||
public bool UseMetadata { get; init; } = true;
|
||||
public bool UseMetadata { get; init; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// When true, checks for duplicate property name hashes during serialization (UseMetadata mode).
|
||||
|
|
|
|||
|
|
@ -31,6 +31,15 @@ public abstract class BinaryPropertyAccessorBase : PropertyAccessorBase
|
|||
/// </summary>
|
||||
public Func<object, object?> DynamicGetter => _dynamicGetter;
|
||||
|
||||
/// <summary>
|
||||
/// Pre-computed expected type code for markerless serialization (UseMetadata=false).
|
||||
/// When set, the serializer skips writing the type marker byte and the deserializer
|
||||
/// uses this value instead of reading from the stream.
|
||||
/// null = marker must be written/read (bool, enum, string, object, collection, nullable value types).
|
||||
/// Non-null = markerless (non-nullable value types: int, long, double, float, decimal, Guid, DateTime, byte, short, ushort, uint, ulong).
|
||||
/// </summary>
|
||||
public byte? ExpectedTypeCode { get; }
|
||||
|
||||
protected BinaryPropertyAccessorBase(PropertyInfo prop, Type declaringType)
|
||||
: base(prop, declaringType)
|
||||
{
|
||||
|
|
@ -39,5 +48,28 @@ public abstract class BinaryPropertyAccessorBase : PropertyAccessorBase
|
|||
// Cache string intern attribute (inherit: true to check base class properties)
|
||||
var attr = prop.GetCustomAttribute<AcStringInternAttribute>(inherit: true);
|
||||
IsStringInternProperty = attr?.Enabled;
|
||||
|
||||
ExpectedTypeCode = ComputeExpectedTypeCode(AccessorType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps AccessorType to the BinaryTypeCode that would normally be written as marker.
|
||||
/// Returns null for types that always need a stream marker (bool, enum, string, object/nullable).
|
||||
/// </summary>
|
||||
private static byte? ComputeExpectedTypeCode(PropertyAccessorType accessorType) => accessorType switch
|
||||
{
|
||||
PropertyAccessorType.Int32 => BinaryTypeCode.Int32,
|
||||
PropertyAccessorType.Int64 => BinaryTypeCode.Int64,
|
||||
PropertyAccessorType.Double => BinaryTypeCode.Float64,
|
||||
PropertyAccessorType.Single => BinaryTypeCode.Float32,
|
||||
PropertyAccessorType.Decimal => BinaryTypeCode.Decimal,
|
||||
PropertyAccessorType.DateTime => BinaryTypeCode.DateTime,
|
||||
PropertyAccessorType.Guid => BinaryTypeCode.Guid,
|
||||
PropertyAccessorType.Byte => BinaryTypeCode.UInt8,
|
||||
PropertyAccessorType.Int16 => BinaryTypeCode.Int16,
|
||||
PropertyAccessorType.UInt16 => BinaryTypeCode.UInt16,
|
||||
PropertyAccessorType.UInt32 => BinaryTypeCode.UInt32,
|
||||
PropertyAccessorType.UInt64 => BinaryTypeCode.UInt64,
|
||||
_ => null // Bool, Enum, String, Object — always read marker from stream
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,11 +23,20 @@ public abstract class BinaryPropertySetterBase : PropertySetterBase
|
|||
/// </summary>
|
||||
public bool IsCollection { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Pre-computed expected type code for markerless deserialization (UseMetadata=false).
|
||||
/// When set, the deserializer uses this value instead of reading a marker from the stream.
|
||||
/// null = marker must be read from stream (bool, enum, string, object, collection, nullable value types).
|
||||
/// Non-null = markerless (non-nullable value types).
|
||||
/// </summary>
|
||||
public byte? ExpectedTypeCode { get; }
|
||||
|
||||
protected BinaryPropertySetterBase(PropertyInfo prop, Type declaringType)
|
||||
: base(prop, declaringType)
|
||||
{
|
||||
IsCollection = IsCollectionTypeCheck(PropertyType);
|
||||
IsComplexType = IsComplex(PropertyType);
|
||||
ExpectedTypeCode = ComputeExpectedTypeCode(AccessorType);
|
||||
}
|
||||
|
||||
public override void SetValue(object target, object? value)
|
||||
|
|
@ -83,4 +92,25 @@ public abstract class BinaryPropertySetterBase : PropertySetterBase
|
|||
if (ReferenceEquals(actualType, DateTimeOffsetType)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps AccessorType to the BinaryTypeCode that would normally be read as marker.
|
||||
/// Returns null for types that always need a stream marker (bool, enum, string, object/nullable).
|
||||
/// </summary>
|
||||
private static byte? ComputeExpectedTypeCode(PropertyAccessorType accessorType) => accessorType switch
|
||||
{
|
||||
PropertyAccessorType.Int32 => BinaryTypeCode.Int32,
|
||||
PropertyAccessorType.Int64 => BinaryTypeCode.Int64,
|
||||
PropertyAccessorType.Double => BinaryTypeCode.Float64,
|
||||
PropertyAccessorType.Single => BinaryTypeCode.Float32,
|
||||
PropertyAccessorType.Decimal => BinaryTypeCode.Decimal,
|
||||
PropertyAccessorType.DateTime => BinaryTypeCode.DateTime,
|
||||
PropertyAccessorType.Guid => BinaryTypeCode.Guid,
|
||||
PropertyAccessorType.Byte => BinaryTypeCode.UInt8,
|
||||
PropertyAccessorType.Int16 => BinaryTypeCode.Int16,
|
||||
PropertyAccessorType.UInt16 => BinaryTypeCode.UInt16,
|
||||
PropertyAccessorType.UInt32 => BinaryTypeCode.UInt32,
|
||||
PropertyAccessorType.UInt64 => BinaryTypeCode.UInt64,
|
||||
_ => null // Bool, Enum, String, Object — always read marker from stream
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue