diff --git a/AyCode.Core/Extensions/AcBinarySerializer.cs b/AyCode.Core/Extensions/AcBinarySerializer.cs
index 881abe4..562336d 100644
--- a/AyCode.Core/Extensions/AcBinarySerializer.cs
+++ b/AyCode.Core/Extensions/AcBinarySerializer.cs
@@ -1,6 +1,7 @@
using System.Buffers;
using System.Collections;
using System.Collections.Concurrent;
+using System.Collections.Generic;
using System.Globalization;
using System.Linq.Expressions;
using System.Reflection;
@@ -47,34 +48,10 @@ public static class AcBinarySerializer
return [BinaryTypeCode.Null];
}
- var type = value.GetType();
-
- // Use context-based serialization for all types to ensure consistent format
- var context = BinarySerializationContextPool.Get(options);
+ var runtimeType = value.GetType();
+ var context = SerializeCore(value, runtimeType, options);
try
{
- // Reserve space for header
- context.WriteHeaderPlaceholder();
-
- // Phase 1: If reference handling enabled, scan for multi-referenced objects
- if (options.UseReferenceHandling && !IsPrimitiveOrStringFast(type))
- {
- ScanReferences(value, context, 0);
- }
-
- // Phase 2: If metadata enabled, collect property names
- if (options.UseMetadata && !IsPrimitiveOrStringFast(type))
- {
- CollectPropertyNames(value, context, 0);
- }
-
- // Write metadata section
- context.WriteMetadata();
-
- // Phase 3: Write the actual data
- WriteValue(value, type, context, 0);
-
- // Finalize and return
return context.ToArray();
}
finally
@@ -97,26 +74,10 @@ public static class AcBinarySerializer
return;
}
- var type = value.GetType();
- var context = BinarySerializationContextPool.Get(options);
+ var runtimeType = value.GetType();
+ var context = SerializeCore(value, runtimeType, options);
try
{
- context.WriteHeaderPlaceholder();
-
- if (options.UseReferenceHandling && !IsPrimitiveOrStringFast(type))
- {
- ScanReferences(value, context, 0);
- }
-
- if (options.UseMetadata && !IsPrimitiveOrStringFast(type))
- {
- CollectPropertyNames(value, context, 0);
- }
-
- context.WriteMetadata();
- WriteValue(value, type, context, 0);
-
- // Write directly to the IBufferWriter instead of creating a new array
context.WriteTo(writer);
}
finally
@@ -133,25 +94,10 @@ public static class AcBinarySerializer
{
if (value == null) return 1;
- var type = value.GetType();
- var context = BinarySerializationContextPool.Get(options);
+ var runtimeType = value.GetType();
+ var context = SerializeCore(value, runtimeType, options);
try
{
- context.WriteHeaderPlaceholder();
-
- if (options.UseReferenceHandling && !IsPrimitiveOrStringFast(type))
- {
- ScanReferences(value, context, 0);
- }
-
- if (options.UseMetadata && !IsPrimitiveOrStringFast(type))
- {
- CollectPropertyNames(value, context, 0);
- }
-
- context.WriteMetadata();
- WriteValue(value, type, context, 0);
-
return context.Position;
}
finally
@@ -160,6 +106,49 @@ public static class AcBinarySerializer
}
}
+ ///
+ /// Serialize object and keep the pooled buffer for zero-copy consumers.
+ /// Caller must dispose the returned result to release the buffer.
+ ///
+ public static BinarySerializationResult SerializeToPooledBuffer(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);
+ }
+
+ context.WriteMetadata();
+ WriteValue(value, runtimeType, context, 0);
+ return context;
+ }
+
#endregion
#region Reference Scanning
@@ -205,33 +194,28 @@ public static class AcBinarySerializer
#endregion
- #region Property Name Collection
+ #region Property Metadata Registration
- private static void CollectPropertyNames(object? value, BinarySerializationContext context, int depth)
+ private static void RegisterMetadataForType(Type type, BinarySerializationContext context, HashSet? visited = null)
{
- if (value == null || depth > context.MaxDepth) return;
-
- var type = value.GetType();
if (IsPrimitiveOrStringFast(type)) return;
- if (value is byte[]) return;
+ visited ??= new HashSet();
+ if (!visited.Add(type)) return;
- if (value is IDictionary dictionary)
+ if (IsDictionaryType(type, out var keyType, out var valueType))
{
- foreach (DictionaryEntry entry in dictionary)
- {
- if (entry.Value != null)
- CollectPropertyNames(entry.Value, context, depth + 1);
- }
+ if (keyType != null) RegisterMetadataForType(keyType, context, visited);
+ if (valueType != null) RegisterMetadataForType(valueType, context, visited);
return;
}
- if (value is IEnumerable enumerable && !ReferenceEquals(type, StringType))
+ if (typeof(IEnumerable).IsAssignableFrom(type) && !ReferenceEquals(type, StringType))
{
- foreach (var item in enumerable)
+ var elementType = GetCollectionElementType(type);
+ if (elementType != null)
{
- if (item != null)
- CollectPropertyNames(item, context, depth + 1);
+ RegisterMetadataForType(elementType, context, visited);
}
return;
}
@@ -240,12 +224,46 @@ public static class AcBinarySerializer
foreach (var prop in metadata.Properties)
{
context.RegisterPropertyName(prop.Name);
- var propValue = prop.GetValue(value);
- if (propValue != null)
- CollectPropertyNames(propValue, context, depth + 1);
+
+ 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
@@ -606,27 +624,35 @@ public static class AcBinarySerializer
var properties = metadata.Properties;
var propCount = properties.Length;
- // Single-pass: count and collect non-null, non-default properties
- // Use stackalloc for small property counts to avoid allocation
- Span validIndices = propCount <= 32 ? stackalloc int[propCount] : new int[propCount];
+ const int StackThreshold = 64;
+ byte[]? rentedStates = null;
+ Span propertyStates = propCount <= StackThreshold
+ ? stackalloc byte[propCount]
+ : (rentedStates = context.RentPropertyStateBuffer(propCount)).AsSpan(0, propCount);
+ propertyStates.Clear();
var writtenCount = 0;
for (var i = 0; i < propCount; i++)
{
- var prop = properties[i];
- if (IsPropertyDefaultOrNull(value, prop))
+ if (IsPropertyDefaultOrNull(value, properties[i]))
+ {
+ propertyStates[i] = 0;
continue;
- validIndices[writtenCount++] = i;
+ }
+
+ propertyStates[i] = 1;
+ writtenCount++;
}
context.WriteVarUInt((uint)writtenCount);
- // Write only the valid properties
- for (var j = 0; j < writtenCount; j++)
+ for (var i = 0; i < propCount; i++)
{
- var prop = properties[validIndices[j]];
+ if (propertyStates[i] == 0)
+ continue;
+
+ var prop = properties[i];
- // Write property index or name
if (context.UseMetadata)
{
var propIndex = context.GetPropertyNameIndex(prop.Name);
@@ -637,9 +663,13 @@ public static class AcBinarySerializer
WriteString(prop.Name, context);
}
- // Use typed writers to avoid boxing
WritePropertyValue(value, prop, context, nextDepth);
}
+
+ if (rentedStates != null)
+ {
+ context.ReturnPropertyStateBuffer(rentedStates);
+ }
}
///
@@ -1089,9 +1119,12 @@ public static class AcBinarySerializer
{
private byte[] _buffer;
private int _position;
+ private int _initialBufferSize;
// Minimum buffer size for ArrayPool (reduces fragmentation)
private const int MinBufferSize = 256;
+ private const int PropertyIndexBufferMaxCache = 512;
+ private const int PropertyStateBufferMaxCache = 512;
// Reference handling
private Dictionary