AyCode.Core/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs

1021 lines
34 KiB
C#

using System.Buffers;
using System.Collections;
using System.Collections.Concurrent;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using static AyCode.Core.Helpers.JsonUtilities;
using ReferenceEqualityComparer = AyCode.Core.Serializers.Jsons.ReferenceEqualityComparer;
namespace AyCode.Core.Serializers.Binaries;
/// <summary>
/// High-performance binary serializer optimized for speed and memory efficiency.
/// Features:
/// - VarInt encoding for compact integers (MessagePack-style)
/// - String interning for repeated strings
/// - Property name table for fast lookup
/// - Reference handling for circular/shared references
/// - Optional metadata for schema evolution
/// - Optimized buffer management with ArrayPool
/// - Zero-allocation hot paths using Span and MemoryMarshal
/// </summary>
public static partial class AcBinarySerializer
{
private static readonly ConcurrentDictionary<Type, BinaryTypeMetadata> TypeMetadataCache = new();
// Pre-computed UTF8 encoder for string operations
private static readonly Encoding Utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
private static readonly Type StringType = typeof(string);
private static readonly Type GuidType = typeof(Guid);
private static readonly Type DateTimeOffsetType = typeof(DateTimeOffset);
private static readonly Type TimeSpanType = typeof(TimeSpan);
private static readonly Type IntType = typeof(int);
private static readonly Type LongType = typeof(long);
private static readonly Type FloatType = typeof(float);
private static readonly Type DoubleType = typeof(double);
private static readonly Type DecimalType = typeof(decimal);
private static readonly Type BoolType = typeof(bool);
private static readonly Type DateTimeType = typeof(DateTime);
#region Public API
/// <summary>
/// Serialize object to binary with default options.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte[] Serialize<T>(T value) => Serialize(value, AcBinarySerializerOptions.Default);
/// <summary>
/// Serialize object to binary with specified options.
/// </summary>
public static byte[] Serialize<T>(T value, AcBinarySerializerOptions options)
{
if (value == null)
{
return [BinaryTypeCode.Null];
}
var runtimeType = value.GetType();
var context = SerializeCore(value, runtimeType, options);
try
{
return context.ToArray();
}
finally
{
BinarySerializationContextPool.Return(context);
}
}
/// <summary>
/// Serialize object to an IBufferWriter for zero-copy scenarios.
/// This avoids the final ToArray() allocation by writing directly to the caller's buffer.
/// </summary>
public static void Serialize<T>(T value, IBufferWriter<byte> writer, AcBinarySerializerOptions options)
{
if (value == null)
{
var span = writer.GetSpan(1);
span[0] = BinaryTypeCode.Null;
writer.Advance(1);
return;
}
var runtimeType = value.GetType();
var context = SerializeCore(value, runtimeType, options);
try
{
context.WriteTo(writer);
}
finally
{
BinarySerializationContextPool.Return(context);
}
}
/// <summary>
/// Get the serialized size without allocating the final array.
/// Useful for pre-allocating buffers.
/// </summary>
public static int GetSerializedSize<T>(T value, AcBinarySerializerOptions options)
{
if (value == null) return 1;
var runtimeType = value.GetType();
var context = SerializeCore(value, runtimeType, options);
try
{
return context.Position;
}
finally
{
BinarySerializationContextPool.Return(context);
}
}
/// <summary>
/// Serialize object and keep the pooled buffer for zero-copy consumers.
/// Caller must dispose the returned result to release the buffer.
/// </summary>
public static BinarySerializationResult SerializeToPooledBuffer<T>(T value, AcBinarySerializerOptions options)
{
if (value == null)
{
return BinarySerializationResult.FromImmutable([BinaryTypeCode.Null]);
}
var runtimeType = value.GetType();
var context = SerializeCore(value, runtimeType, options);
try
{
return context.DetachResult();
}
finally
{
BinarySerializationContextPool.Return(context);
}
}
private static BinarySerializationContext SerializeCore(object value, Type runtimeType, AcBinarySerializerOptions options)
{
var context = BinarySerializationContextPool.Get(options);
context.WriteHeaderPlaceholder();
if (options.UseReferenceHandling && !IsPrimitiveOrStringFast(runtimeType))
{
ScanReferences(value, context, 0);
}
if (options.UseMetadata && !IsPrimitiveOrStringFast(runtimeType))
{
RegisterMetadataForType(runtimeType, context);
}
// Estimate and reserve header space to avoid body shift later
var estimatedHeaderSize = context.EstimateHeaderPayloadSize();
context.ReserveHeaderSpace(estimatedHeaderSize);
WriteValue(value, runtimeType, context, 0);
context.FinalizeHeaderSections();
return context;
}
#endregion
#region Reference Scanning
private static void ScanReferences(object? value, BinarySerializationContext context, int depth)
{
if (value == null || depth > context.MaxDepth) return;
var type = value.GetType();
if (IsPrimitiveOrStringFast(type)) return;
if (!context.TrackForScanning(value)) return;
if (value is byte[]) return; // byte arrays are value types
if (value is IDictionary dictionary)
{
foreach (DictionaryEntry entry in dictionary)
{
if (entry.Value != null)
ScanReferences(entry.Value, context, depth + 1);
}
return;
}
if (value is IEnumerable enumerable && !ReferenceEquals(type, StringType))
{
foreach (var item in enumerable)
{
if (item != null)
ScanReferences(item, context, depth + 1);
}
return;
}
var metadata = GetTypeMetadata(type);
foreach (var prop in metadata.Properties)
{
if (!context.ShouldSerializeProperty(value, prop))
{
continue;
}
var propValue = prop.GetValue(value);
if (propValue != null)
ScanReferences(propValue, context, depth + 1);
}
}
#endregion
#region Property Metadata Registration
private static void RegisterMetadataForType(Type type, BinarySerializationContext context, HashSet<Type>? visited = null)
{
if (IsPrimitiveOrStringFast(type)) return;
visited ??= new HashSet<Type>();
if (!visited.Add(type)) return;
if (IsDictionaryType(type, out var keyType, out var valueType))
{
if (keyType != null) RegisterMetadataForType(keyType, context, visited);
if (valueType != null) RegisterMetadataForType(valueType, context, visited);
return;
}
if (typeof(IEnumerable).IsAssignableFrom(type) && !ReferenceEquals(type, StringType))
{
var elementType = GetCollectionElementType(type);
if (elementType != null)
{
RegisterMetadataForType(elementType, context, visited);
}
return;
}
var metadata = GetTypeMetadata(type);
foreach (var prop in metadata.Properties)
{
if (!context.ShouldIncludePropertyInMetadata(prop))
{
continue;
}
// Use caching registration to avoid dictionary lookup during serialization
context.RegisterPropertyNameAndCache(prop);
if (TryResolveNestedMetadataType(prop.PropertyType, out var nestedType))
{
RegisterMetadataForType(nestedType, context, visited);
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryResolveNestedMetadataType(Type propertyType, out Type nestedType)
{
nestedType = Nullable.GetUnderlyingType(propertyType) ?? propertyType;
if (IsPrimitiveOrStringFast(nestedType))
return false;
if (IsDictionaryType(nestedType, out var _, out var valueType) && valueType != null)
{
if (!IsPrimitiveOrStringFast(valueType))
{
nestedType = valueType;
return true;
}
return false;
}
if (typeof(IEnumerable).IsAssignableFrom(nestedType) && !ReferenceEquals(nestedType, StringType))
{
var elementType = GetCollectionElementType(nestedType);
if (elementType != null && !IsPrimitiveOrStringFast(elementType))
{
nestedType = elementType;
return true;
}
return false;
}
return true;
}
#endregion
#region Value Writing
private static void WriteValue(object? value, Type type, BinarySerializationContext context, int depth)
{
if (value == null)
{
context.WriteByte(BinaryTypeCode.Null);
return;
}
// Try writing as primitive first
if (TryWritePrimitive(value, type, context))
return;
if (depth > context.MaxDepth)
{
context.WriteByte(BinaryTypeCode.Null);
return;
}
// Check for object reference
if (context.UseReferenceHandling && context.TryGetExistingRef(value, out var refId))
{
context.WriteByte(BinaryTypeCode.ObjectRef);
context.WriteVarInt(refId);
return;
}
// Handle byte arrays specially
if (value is byte[] byteArray)
{
WriteByteArray(byteArray, context);
return;
}
// Handle dictionaries
if (value is IDictionary dictionary)
{
WriteDictionary(dictionary, context, depth);
return;
}
// Handle collections/arrays
if (value is IEnumerable enumerable && !ReferenceEquals(type, StringType))
{
WriteArray(enumerable, type, context, depth);
return;
}
// Handle complex objects
WriteObject(value, type, context, depth);
}
/// <summary>
/// Optimized primitive writer using TypeCode dispatch.
/// Avoids Nullable.GetUnderlyingType in hot path by pre-computing in metadata.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryWritePrimitive(object value, Type type, BinarySerializationContext context)
{
// Fast path: check TypeCode first (handles most primitives)
var typeCode = Type.GetTypeCode(type);
switch (typeCode)
{
case TypeCode.Int32:
WriteInt32((int)value, context);
return true;
case TypeCode.Int64:
WriteInt64((long)value, context);
return true;
case TypeCode.Boolean:
context.WriteByte((bool)value ? BinaryTypeCode.True : BinaryTypeCode.False);
return true;
case TypeCode.Double:
WriteFloat64Unsafe((double)value, context);
return true;
case TypeCode.String:
WriteString((string)value, context);
return true;
case TypeCode.Single:
WriteFloat32Unsafe((float)value, context);
return true;
case TypeCode.Decimal:
WriteDecimalUnsafe((decimal)value, context);
return true;
case TypeCode.DateTime:
WriteDateTimeUnsafe((DateTime)value, context);
return true;
case TypeCode.Byte:
context.WriteByte(BinaryTypeCode.UInt8);
context.WriteByte((byte)value);
return true;
case TypeCode.Int16:
WriteInt16Unsafe((short)value, context);
return true;
case TypeCode.UInt16:
WriteUInt16Unsafe((ushort)value, context);
return true;
case TypeCode.UInt32:
WriteUInt32((uint)value, context);
return true;
case TypeCode.UInt64:
WriteUInt64((ulong)value, context);
return true;
case TypeCode.SByte:
context.WriteByte(BinaryTypeCode.Int8);
context.WriteByte(unchecked((byte)(sbyte)value));
return true;
case TypeCode.Char:
WriteCharUnsafe((char)value, context);
return true;
}
// Handle nullable types
var underlyingType = Nullable.GetUnderlyingType(type);
if (underlyingType != null)
{
return TryWritePrimitive(value, underlyingType, context);
}
// Handle special types by reference comparison (faster than type equality)
if (ReferenceEquals(type, GuidType))
{
WriteGuidUnsafe((Guid)value, context);
return true;
}
if (ReferenceEquals(type, DateTimeOffsetType))
{
WriteDateTimeOffsetUnsafe((DateTimeOffset)value, context);
return true;
}
if (ReferenceEquals(type, TimeSpanType))
{
WriteTimeSpanUnsafe((TimeSpan)value, context);
return true;
}
if (type.IsEnum)
{
WriteEnum(value, context);
return true;
}
return false;
}
#endregion
#region Optimized Primitive Writers using MemoryMarshal
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WriteInt32(int value, BinarySerializationContext context)
{
if (BinaryTypeCode.TryEncodeTinyInt(value, out var tiny))
{
context.WriteByte(tiny);
return;
}
context.WriteByte(BinaryTypeCode.Int32);
context.WriteVarInt(value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WriteInt64(long value, BinarySerializationContext context)
{
if (value >= int.MinValue && value <= int.MaxValue)
{
WriteInt32((int)value, context);
return;
}
context.WriteByte(BinaryTypeCode.Int64);
context.WriteVarLong(value);
}
/// <summary>
/// Optimized float64 writer using batched write.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WriteFloat64Unsafe(double value, BinarySerializationContext context)
{
context.WriteTypeCodeAndRaw(BinaryTypeCode.Float64, value);
}
/// <summary>
/// Optimized float32 writer using batched write.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WriteFloat32Unsafe(float value, BinarySerializationContext context)
{
context.WriteTypeCodeAndRaw(BinaryTypeCode.Float32, value);
}
/// <summary>
/// Optimized decimal writer using direct memory copy of bits.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WriteDecimalUnsafe(decimal value, BinarySerializationContext context)
{
context.WriteByte(BinaryTypeCode.Decimal);
context.WriteDecimalBits(value);
}
/// <summary>
/// Optimized DateTime writer.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WriteDateTimeUnsafe(DateTime value, BinarySerializationContext context)
{
context.WriteByte(BinaryTypeCode.DateTime);
context.WriteDateTimeBits(value);
}
/// <summary>
/// Optimized Guid writer using direct memory copy.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WriteGuidUnsafe(Guid value, BinarySerializationContext context)
{
context.WriteByte(BinaryTypeCode.Guid);
context.WriteGuidBits(value);
}
/// <summary>
/// Optimized DateTimeOffset writer.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WriteDateTimeOffsetUnsafe(DateTimeOffset value, BinarySerializationContext context)
{
context.WriteByte(BinaryTypeCode.DateTimeOffset);
context.WriteDateTimeOffsetBits(value);
}
/// <summary>
/// Optimized TimeSpan writer.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WriteTimeSpanUnsafe(TimeSpan value, BinarySerializationContext context)
{
context.WriteTypeCodeAndRaw(BinaryTypeCode.TimeSpan, value.Ticks);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WriteInt16Unsafe(short value, BinarySerializationContext context)
{
context.WriteTypeCodeAndRaw(BinaryTypeCode.Int16, value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WriteUInt16Unsafe(ushort value, BinarySerializationContext context)
{
context.WriteTypeCodeAndRaw(BinaryTypeCode.UInt16, value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WriteUInt32(uint value, BinarySerializationContext context)
{
context.WriteByte(BinaryTypeCode.UInt32);
context.WriteVarUInt(value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WriteUInt64(ulong value, BinarySerializationContext context)
{
context.WriteByte(BinaryTypeCode.UInt64);
context.WriteVarULong(value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WriteCharUnsafe(char value, BinarySerializationContext context)
{
context.WriteByte(BinaryTypeCode.Char);
context.WriteRaw(value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WriteEnum(object value, BinarySerializationContext context
)
{
var intValue = Convert.ToInt32(value);
if (BinaryTypeCode.TryEncodeTinyInt(intValue, out var tiny))
{
context.WriteByte(BinaryTypeCode.Enum);
context.WriteByte(tiny);
return;
}
context.WriteByte(BinaryTypeCode.Enum);
context.WriteByte(BinaryTypeCode.Int32);
context.WriteVarInt(intValue);
}
/// <summary>
/// Optimized string writer with span-based UTF8 encoding.
/// Uses stackalloc for small strings to avoid allocations.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WriteString(string value, BinarySerializationContext context)
{
if (value.Length == 0)
{
context.WriteByte(BinaryTypeCode.StringEmpty);
return;
}
if (context.UseStringInterning && value.Length >= context.MinStringInternLength)
{
var index = context.RegisterInternedString(value);
context.WriteByte(BinaryTypeCode.StringInterned);
context.WriteVarUInt((uint)index);
return;
}
// Első előfordulás vagy nincs interning - sima string
context.WriteByte(BinaryTypeCode.String);
context.WriteStringUtf8(value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WriteByteArray(byte[] value, BinarySerializationContext context)
{
context.WriteByte(BinaryTypeCode.ByteArray);
context.WriteVarUInt((uint)value.Length);
context.WriteBytes(value);
}
#endregion
#region Complex Type Writers
private static void WriteObject(object value, Type type, BinarySerializationContext context, int depth)
{
context.WriteByte(BinaryTypeCode.Object);
// Register object reference if needed
if (context.UseReferenceHandling && context.ShouldWriteRef(value, out var refId))
{
context.WriteVarInt(refId);
context.MarkAsWritten(value, refId);
}
else if (context.UseReferenceHandling)
{
context.WriteVarInt(-1); // No ref ID
}
var metadata = GetTypeMetadata(type);
var nextDepth = depth + 1;
var properties = metadata.Properties;
var propCount = properties.Length;
// Reserve space for property count (will patch later)
var countPosition = context.Position;
context.WriteVarUInt(0); // Placeholder - will be patched
var writtenCount = 0;
// Single pass: check and write in one iteration
for (var i = 0; i < propCount; i++)
{
var prop = properties[i];
// Skip if filter says no
if (context.PropertyFilter != null && !context.ShouldSerializeProperty(value, prop))
continue;
// Skip default/null values
if (IsPropertyDefaultOrNull(value, prop))
continue;
// Write property name/index
if (context.UseMetadata)
{
var propIndex = prop.CachedPropertyNameIndex >= 0
? prop.CachedPropertyNameIndex
: context.GetPropertyNameIndex(prop.Name);
context.WriteVarUInt((uint)propIndex);
}
else
{
context.WritePreencodedPropertyName(prop.NameUtf8);
}
// Write property value
WritePropertyValue(value, prop, context, nextDepth);
writtenCount++;
}
// Patch the property count
context.PatchVarUInt(countPosition, (uint)writtenCount);
}
/// <summary>
/// Checks if a property value is null or default without boxing for value types.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsPropertyDefaultOrNull(object obj, BinaryPropertyAccessor prop)
{
switch (prop.AccessorType)
{
case PropertyAccessorType.Int32:
return prop.GetInt32(obj) == 0;
case PropertyAccessorType.Int64:
return prop.GetInt64(obj) == 0L;
case PropertyAccessorType.Boolean:
return !prop.GetBoolean(obj);
case PropertyAccessorType.Double:
return prop.GetDouble(obj) == 0.0;
case PropertyAccessorType.Single:
return prop.GetSingle(obj) == 0f;
case PropertyAccessorType.Decimal:
return prop.GetDecimal(obj) == 0m;
case PropertyAccessorType.Byte:
return prop.GetByte(obj) == 0;
case PropertyAccessorType.Int16:
return prop.GetInt16(obj) == 0;
case PropertyAccessorType.UInt16:
return prop.GetUInt16(obj) == 0;
case PropertyAccessorType.UInt32:
return prop.GetUInt32(obj) == 0;
case PropertyAccessorType.UInt64:
return prop.GetUInt64(obj) == 0;
case PropertyAccessorType.Guid:
return prop.GetGuid(obj) == Guid.Empty;
case PropertyAccessorType.Enum:
return prop.GetEnumAsInt32(obj) == 0;
case PropertyAccessorType.DateTime:
// DateTime default is not typically skipped
return false;
default:
// Object type - use regular getter
var value = prop.GetValue(obj);
if (value == null) return true;
if (prop.TypeCode == TypeCode.String) return string.IsNullOrEmpty((string)value);
return false;
}
}
/// <summary>
/// Writes a property value using typed getters to avoid boxing.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void WritePropertyValue(object obj, BinaryPropertyAccessor prop, BinarySerializationContext context, int depth)
{
switch (prop.AccessorType)
{
case PropertyAccessorType.Int32:
WriteInt32(prop.GetInt32(obj), context);
return;
case PropertyAccessorType.Int64:
WriteInt64(prop.GetInt64(obj), context);
return;
case PropertyAccessorType.Boolean:
context.WriteByte(prop.GetBoolean(obj) ? BinaryTypeCode.True : BinaryTypeCode.False);
return;
case PropertyAccessorType.Double:
WriteFloat64Unsafe(prop.GetDouble(obj), context);
return;
case PropertyAccessorType.Single:
WriteFloat32Unsafe(prop.GetSingle(obj), context);
return;
case PropertyAccessorType.Decimal:
WriteDecimalUnsafe(prop.GetDecimal(obj), context);
return;
case PropertyAccessorType.DateTime:
WriteDateTimeUnsafe(prop.GetDateTime(obj), context);
return;
case PropertyAccessorType.Byte:
context.WriteByte(BinaryTypeCode.UInt8);
context.WriteByte(prop.GetByte(obj));
return;
case PropertyAccessorType.Int16:
WriteInt16Unsafe(prop.GetInt16(obj), context);
return;
case PropertyAccessorType.UInt16:
WriteUInt16Unsafe(prop.GetUInt16(obj), context);
return;
case PropertyAccessorType.UInt32:
WriteUInt32(prop.GetUInt32(obj), context);
return;
case PropertyAccessorType.UInt64:
WriteUInt64(prop.GetUInt64(obj), context);
return;
case PropertyAccessorType.Guid:
WriteGuidUnsafe(prop.GetGuid(obj), context);
return;
case PropertyAccessorType.Enum:
var enumValue = prop.GetEnumAsInt32(obj);
if (BinaryTypeCode.TryEncodeTinyInt(enumValue, out var tiny))
{
context.WriteByte(BinaryTypeCode.Enum);
context.WriteByte(tiny);
}
else
{
context.WriteByte(BinaryTypeCode.Enum);
context.WriteByte(BinaryTypeCode.Int32);
context.WriteVarInt(enumValue);
}
return;
default:
// Fallback to object getter for reference types
var value = prop.GetValue(obj);
WriteValue(value, prop.PropertyType, context, depth);
return;
}
}
#endregion
#region Specialized Array Writers
/// <summary>
/// Optimized array writer with specialized paths for primitive arrays.
/// </summary>
private static void WriteArray(IEnumerable enumerable, Type type, BinarySerializationContext context, int depth)
{
context.WriteByte(BinaryTypeCode.Array);
var nextDepth = depth + 1;
// Optimized path for primitive arrays
var elementType = GetCollectionElementType(type);
if (elementType != null && type.IsArray)
{
if (TryWritePrimitiveArray(enumerable, elementType, context))
return;
}
// For IList, we can write the count directly
if (enumerable is IList list)
{
var count = list.Count;
context.WriteVarUInt((uint)count);
for (var i = 0; i < count; i++)
{
var item = list[i];
var itemType = item?.GetType() ?? typeof(object);
WriteValue(item, itemType, context, nextDepth);
}
return;
}
// For other IEnumerable, collect first
var items = new List<object?>();
foreach (var item in enumerable)
{
items.Add(item);
}
context.WriteVarUInt((uint)items.Count);
foreach (var item in items)
{
var itemType = item?.GetType() ?? typeof(object);
WriteValue(item, itemType, context, nextDepth);
}
}
/// <summary>
/// Specialized array writer for primitive arrays using bulk memory operations.
/// Optimized for Blazor Hybrid compatibility (WASM, Android, Windows, iOS).
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryWritePrimitiveArray(IEnumerable enumerable, Type elementType, BinarySerializationContext context)
{
// Int32 array - very common case
if (ReferenceEquals(elementType, IntType) && enumerable is int[] intArray)
{
context.WriteVarUInt((uint)intArray.Length);
context.WriteInt32ArrayOptimized(intArray);
return true;
}
// Double array - bulk write as raw bytes
if (ReferenceEquals(elementType, DoubleType) && enumerable is double[] doubleArray)
{
context.WriteVarUInt((uint)doubleArray.Length);
context.WriteDoubleArrayBulk(doubleArray);
return true;
}
// Long array
if (ReferenceEquals(elementType, LongType) && enumerable is long[] longArray)
{
context.WriteVarUInt((uint)longArray.Length);
context.WriteLongArrayOptimized(longArray);
return true;
}
// Float array - bulk write as raw bytes
if (ReferenceEquals(elementType, FloatType) && enumerable is float[] floatArray)
{
context.WriteVarUInt((uint)floatArray.Length);
context.WriteFloatArrayBulk(floatArray);
return true;
}
// Bool array - pack as bytes
if (ReferenceEquals(elementType, BoolType) && enumerable is bool[] boolArray)
{
context.WriteVarUInt((uint)boolArray.Length);
for (var i = 0; i < boolArray.Length; i++)
{
context.WriteByte(boolArray[i] ? BinaryTypeCode.True : BinaryTypeCode.False);
}
context.WriteVarUInt((uint)boolArray.Length);
return true;
}
// Guid array - bulk write
if (ReferenceEquals(elementType, GuidType) && enumerable is Guid[] guidArray)
{
context.WriteVarUInt((uint)guidArray.Length);
context.WriteGuidArrayBulk(guidArray);
return true;
}
// Decimal array
if (ReferenceEquals(elementType, DecimalType) && enumerable is decimal[] decimalArray)
{
context.WriteVarUInt((uint)decimalArray.Length);
for (var i = 0; i < decimalArray.Length; i++)
{
WriteDecimalUnsafe(decimalArray[i], context);
}
return true;
}
// DateTime array
if (ReferenceEquals(elementType, DateTimeType) && enumerable is DateTime[] dateTimeArray)
{
context.WriteVarUInt((uint)dateTimeArray.Length);
for (var i = 0; i < dateTimeArray.Length; i++)
{
WriteDateTimeUnsafe(dateTimeArray[i], context);
}
return true;
}
// String array - common case
if (ReferenceEquals(elementType, StringType) && enumerable is string[] stringArray)
{
context.WriteVarUInt((uint)stringArray.Length);
for (var i = 0; i < stringArray.Length; i++)
{
var s = stringArray[i];
if (s == null)
context.WriteByte(BinaryTypeCode.Null);
else
WriteString(s, context);
}
return true;
}
return false;
}
private static void WriteDictionary(IDictionary dictionary, BinarySerializationContext context, int depth)
{
context.WriteByte(BinaryTypeCode.Dictionary);
context.WriteVarUInt((uint)dictionary.Count);
var nextDepth = depth + 1;
foreach (DictionaryEntry entry in dictionary)
{
// Write key
var keyType = entry.Key?.GetType() ?? typeof(object);
WriteValue(entry.Key, keyType, context, nextDepth);
// Write value
var valueType = entry.Value?.GetType() ?? typeof(object);
WriteValue(entry.Value, valueType, context, nextDepth);
}
}
#endregion
#region Serialization Result
// Implementation moved to AcBinarySerializer.BinarySerializationResult.cs
#endregion
#region Context Pool
// Implementation moved to AcBinarySerializer.BinarySerializationContext.cs
#endregion
#region Serialization Context
// Implementation moved to AcBinarySerializer.BinarySerializationContext.cs
#endregion
#region Type Metadata
private static Type? GetCollectionElementType(Type type)
{
if (type.IsArray)
{
return type.GetElementType();
}
if (type.IsGenericType)
{
var args = type.GetGenericArguments();
if (args.Length == 1)
{
return args[0];
}
}
foreach (var iface in type.GetInterfaces())
{
if (iface.IsGenericType && iface.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
return iface.GetGenericArguments()[0];
}
}
return null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static BinaryTypeMetadata GetTypeMetadata(Type type)
=> TypeMetadataCache.GetOrAdd(type, static t => new BinaryTypeMetadata(t));
// Type metadata helpers moved to AcBinarySerializer.BinaryTypeMetadata.cs
#endregion
}