diff --git a/AyCode.Benchmark/SerializationBenchmarks.cs b/AyCode.Benchmark/SerializationBenchmarks.cs
index 84994d3..3ec6494 100644
--- a/AyCode.Benchmark/SerializationBenchmarks.cs
+++ b/AyCode.Benchmark/SerializationBenchmarks.cs
@@ -8,6 +8,10 @@ using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using JsonSerializer = System.Text.Json.JsonSerializer;
+using MongoDB.Bson;
+using MongoDB.Bson.IO;
+using MongoDB.Bson.Serialization;
+using System.IO;
namespace AyCode.Core.Benchmarks;
@@ -53,14 +57,14 @@ public class SimpleBinaryBenchmark
public void Setup()
{
_testData = TestDataFactory.CreatePrimitiveTestData();
- _binaryData = AcBinarySerializer.Serialize(_testData);
+ _binaryData = AcBinarySerializer.Serialize(_testData, AcBinarySerializerOptions.WithoutReferenceHandling());
_jsonData = AcJsonSerializer.Serialize(_testData, AcJsonSerializerOptions.WithoutReferenceHandling());
Console.WriteLine($"Binary: {_binaryData.Length} bytes, JSON: {_jsonData.Length} chars");
}
[Benchmark(Description = "Binary Serialize")]
- public byte[] SerializeBinary() => AcBinarySerializer.Serialize(_testData);
+ public byte[] SerializeBinary() => AcBinarySerializer.Serialize(_testData, AcBinarySerializerOptions.WithoutReferenceHandling());
[Benchmark(Description = "JSON Serialize", Baseline = true)]
public string SerializeJson() => AcJsonSerializer.Serialize(_testData, AcJsonSerializerOptions.WithoutReferenceHandling());
@@ -74,6 +78,7 @@ public class SimpleBinaryBenchmark
///
/// Complex hierarchy benchmark - AcBinary vs JSON only (no MessagePack to isolate the issue).
+/// Uses AcBinary without reference handling.
///
[ShortRunJob]
[MemoryDiagnoser]
@@ -98,7 +103,7 @@ public class ComplexBinaryBenchmark
pointsPerMeasurement: 3);
Console.WriteLine($"Created order with {_testOrder.Items.Count} items");
- _binaryOptions = AcBinarySerializerOptions.Default;
+ _binaryOptions = AcBinarySerializerOptions.WithoutReferenceHandling();
_jsonOptions = AcJsonSerializerOptions.WithoutReferenceHandling();
Console.WriteLine("Serializing AcBinary...");
@@ -129,7 +134,7 @@ public class ComplexBinaryBenchmark
}
///
-/// Full comparison with MessagePack - separate class to isolate potential issues.
+/// Full comparison with MessagePack and BSON - AcBinary uses NO reference handling everywhere.
///
[ShortRunJob]
[MemoryDiagnoser]
@@ -139,6 +144,7 @@ public class MessagePackComparisonBenchmark
private TestOrder _testOrder = null!;
private byte[] _acBinaryData = null!;
private byte[] _msgPackData = null!;
+ private byte[] _bsonData = null!;
private string _jsonData = null!;
private AcBinarySerializerOptions _binaryOptions = null!;
@@ -155,7 +161,7 @@ public class MessagePackComparisonBenchmark
measurementsPerPallet: 2,
pointsPerMeasurement: 3);
- _binaryOptions = AcBinarySerializerOptions.Default;
+ _binaryOptions = AcBinarySerializerOptions.WithoutReferenceHandling();
_msgPackOptions = ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None);
_jsonOptions = AcJsonSerializerOptions.WithoutReferenceHandling();
@@ -175,10 +181,25 @@ public class MessagePackComparisonBenchmark
_msgPackData = Array.Empty();
}
+ // BSON serialization
+ try
+ {
+ Console.WriteLine("Serializing BSON...");
+ var bsonDoc = _testOrder.ToBsonDocument();
+ _bsonData = bsonDoc.ToBson();
+ Console.WriteLine($"BSON size: {_bsonData.Length} bytes");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"BSON serialization failed: {ex.Message}");
+ _bsonData = Array.Empty();
+ }
+
var jsonBytes = Encoding.UTF8.GetByteCount(_jsonData);
Console.WriteLine($"\n=== SIZE COMPARISON ===");
Console.WriteLine($"AcBinary: {_acBinaryData.Length,8:N0} bytes ({100.0 * _acBinaryData.Length / jsonBytes:F1}%)");
Console.WriteLine($"MessagePack: {_msgPackData.Length,8:N0} bytes ({100.0 * _msgPackData.Length / jsonBytes:F1}%)");
+ Console.WriteLine($"BSON: {_bsonData.Length,8:N0} bytes ({100.0 * _bsonData.Length / jsonBytes:F1}%)");
Console.WriteLine($"JSON: {jsonBytes,8:N0} bytes (100.0%)");
}
@@ -188,16 +209,28 @@ public class MessagePackComparisonBenchmark
[Benchmark(Description = "MessagePack Serialize", Baseline = true)]
public byte[] Serialize_MsgPack() => MessagePackSerializer.Serialize(_testOrder, _msgPackOptions);
+ [Benchmark(Description = "BSON Serialize")]
+ public byte[] Serialize_Bson() => _testOrder.ToBsonDocument().ToBson();
+
[Benchmark(Description = "AcBinary Deserialize")]
public TestOrder? Deserialize_AcBinary() => AcBinaryDeserializer.Deserialize(_acBinaryData);
[Benchmark(Description = "MessagePack Deserialize")]
public TestOrder? Deserialize_MsgPack() => MessagePackSerializer.Deserialize(_msgPackData, _msgPackOptions);
+
+ [Benchmark(Description = "BSON Deserialize")]
+ public TestOrder? Deserialize_Bson()
+ {
+ if (_bsonData == null || _bsonData.Length == 0) return null;
+ using var ms = new MemoryStream(_bsonData);
+ using var reader = new BsonBinaryReader(ms);
+ return BsonSerializer.Deserialize(reader);
+ }
}
///
/// Comprehensive AcBinary vs MessagePack comparison benchmark.
-/// Tests: WithRef, NoRef, Populate, Serialize, Deserialize, Size
+/// Tests: NoRef (everywhere), Populate, Serialize, Deserialize, Size
///
[ShortRunJob]
[MemoryDiagnoser]
@@ -214,6 +247,7 @@ public class AcBinaryVsMessagePackFullBenchmark
// Serialized data - MessagePack
private byte[] _msgPackData = null!;
+ private byte[] _bsonData = null!;
// Options
private AcBinarySerializerOptions _withRefOptions = null!;
@@ -238,8 +272,8 @@ public class AcBinaryVsMessagePackFullBenchmark
sharedUser: sharedUser,
sharedMetadata: sharedMeta);
- // Setup options
- _withRefOptions = AcBinarySerializerOptions.Default; // WithRef by default
+ // Setup options - enforce no reference handling everywhere
+ _withRefOptions = AcBinarySerializerOptions.WithoutReferenceHandling();
_noRefOptions = AcBinarySerializerOptions.WithoutReferenceHandling();
_msgPackOptions = ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None);
@@ -248,6 +282,16 @@ public class AcBinaryVsMessagePackFullBenchmark
_acBinaryNoRef = AcBinarySerializer.Serialize(_testOrder, _noRefOptions);
_msgPackData = MessagePackSerializer.Serialize(_testOrder, _msgPackOptions);
+ // BSON
+ try
+ {
+ _bsonData = _testOrder.ToBsonDocument().ToBson();
+ }
+ catch
+ {
+ _bsonData = Array.Empty();
+ }
+
// Create populate target
_populateTarget = new TestOrder { Id = _testOrder.Id };
foreach (var item in _testOrder.Items)
@@ -262,59 +306,66 @@ public class AcBinaryVsMessagePackFullBenchmark
private void PrintSizeComparison()
{
Console.WriteLine("\n" + new string('=', 60));
- Console.WriteLine("?? SIZE COMPARISON (AcBinary vs MessagePack)");
+ Console.WriteLine("?? SIZE COMPARISON (AcBinary vs MessagePack vs BSON)");
Console.WriteLine(new string('=', 60));
Console.WriteLine($" AcBinary WithRef: {_acBinaryWithRef.Length,8:N0} bytes");
Console.WriteLine($" AcBinary NoRef: {_acBinaryNoRef.Length,8:N0} bytes");
Console.WriteLine($" MessagePack: {_msgPackData.Length,8:N0} bytes");
+ Console.WriteLine($" BSON: {_bsonData.Length,8:N0} bytes");
Console.WriteLine(new string('-', 60));
- Console.WriteLine($" AcBinary/MsgPack: {100.0 * _acBinaryWithRef.Length / _msgPackData.Length:F1}% (WithRef)");
- Console.WriteLine($" AcBinary/MsgPack: {100.0 * _acBinaryNoRef.Length / _msgPackData.Length:F1}% (NoRef)");
+ Console.WriteLine($" AcBinary/MsgPack: {100.0 * _acBinaryWithRef.Length / Math.Max(1, _msgPackData.Length):F1}% (WithRef - actually NoRef)");
+ Console.WriteLine($" AcBinary/MsgPack: {100.0 * _acBinaryNoRef.Length / Math.Max(1, _msgPackData.Length):F1}% (NoRef)");
Console.WriteLine(new string('=', 60) + "\n");
}
#region Serialize Benchmarks
- [Benchmark(Description = "AcBinary Serialize WithRef")]
- public byte[] Serialize_AcBinary_WithRef() => AcBinarySerializer.Serialize(_testOrder, _withRefOptions);
-
[Benchmark(Description = "AcBinary Serialize NoRef")]
public byte[] Serialize_AcBinary_NoRef() => AcBinarySerializer.Serialize(_testOrder, _noRefOptions);
[Benchmark(Description = "MessagePack Serialize", Baseline = true)]
public byte[] Serialize_MsgPack() => MessagePackSerializer.Serialize(_testOrder, _msgPackOptions);
+ [Benchmark(Description = "BSON Serialize")]
+ public byte[] Serialize_Bson() => _testOrder.ToBsonDocument().ToBson();
+
#endregion
#region Deserialize Benchmarks
- [Benchmark(Description = "AcBinary Deserialize WithRef")]
- public TestOrder? Deserialize_AcBinary_WithRef() => AcBinaryDeserializer.Deserialize(_acBinaryWithRef);
-
[Benchmark(Description = "AcBinary Deserialize NoRef")]
public TestOrder? Deserialize_AcBinary_NoRef() => AcBinaryDeserializer.Deserialize(_acBinaryNoRef);
[Benchmark(Description = "MessagePack Deserialize")]
public TestOrder? Deserialize_MsgPack() => MessagePackSerializer.Deserialize(_msgPackData, _msgPackOptions);
+ [Benchmark(Description = "BSON Deserialize")]
+ public TestOrder? Deserialize_Bson()
+ {
+ if (_bsonData == null || _bsonData.Length == 0) return null;
+ using var ms = new MemoryStream(_bsonData);
+ using var reader = new BsonBinaryReader(ms);
+ return BsonSerializer.Deserialize(reader);
+ }
+
#endregion
#region Populate Benchmarks
- [Benchmark(Description = "AcBinary Populate WithRef")]
- public void Populate_AcBinary_WithRef()
+ [Benchmark(Description = "AcBinary Populate NoRef")]
+ public void Populate_AcBinary_NoRef()
{
// Create fresh target each time to avoid state accumulation
var target = CreatePopulateTarget();
- AcBinaryDeserializer.Populate(_acBinaryWithRef, target);
+ AcBinaryDeserializer.Populate(_acBinaryNoRef, target);
}
- [Benchmark(Description = "AcBinary PopulateMerge WithRef")]
- public void PopulateMerge_AcBinary_WithRef()
+ [Benchmark(Description = "AcBinary PopulateMerge NoRef")]
+ public void PopulateMerge_AcBinary_NoRef()
{
// Create fresh target each time to avoid state accumulation
var target = CreatePopulateTarget();
- AcBinaryDeserializer.PopulateMerge(_acBinaryWithRef.AsSpan(), target);
+ AcBinaryDeserializer.PopulateMerge(_acBinaryNoRef.AsSpan(), target);
}
private TestOrder CreatePopulateTarget()
@@ -332,6 +383,7 @@ public class AcBinaryVsMessagePackFullBenchmark
///
/// Detailed size comparison - not a performance benchmark, just size output.
+/// Now includes BSON size output and uses AcBinary without reference handling.
///
[ShortRunJob]
[MemoryDiagnoser]
@@ -349,7 +401,7 @@ public class SizeComparisonBenchmark
public void Setup()
{
_msgPackOptions = ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None);
- _withRefOptions = AcBinarySerializerOptions.Default;
+ _withRefOptions = AcBinarySerializerOptions.WithoutReferenceHandling();
_noRefOptions = AcBinarySerializerOptions.WithoutReferenceHandling();
// Small order
@@ -379,7 +431,7 @@ public class SizeComparisonBenchmark
private void PrintDetailedSizeComparison()
{
Console.WriteLine("\n" + new string('=', 80));
- Console.WriteLine("?? DETAILED SIZE COMPARISON: AcBinary vs MessagePack");
+ Console.WriteLine("?? DETAILED SIZE COMPARISON: AcBinary vs MessagePack vs BSON");
Console.WriteLine(new string('=', 80));
PrintOrderSize("Small Order (1x1x1x2)", _smallOrder);
@@ -394,11 +446,14 @@ public class SizeComparisonBenchmark
var acWithRef = AcBinarySerializer.Serialize(order, _withRefOptions);
var acNoRef = AcBinarySerializer.Serialize(order, _noRefOptions);
var msgPack = MessagePackSerializer.Serialize(order, _msgPackOptions);
+ byte[] bson;
+ try { bson = order.ToBsonDocument().ToBson(); } catch { bson = Array.Empty(); }
Console.WriteLine($"\n {name}:");
- Console.WriteLine($" AcBinary WithRef: {acWithRef.Length,8:N0} bytes ({100.0 * acWithRef.Length / msgPack.Length,5:F1}% of MsgPack)");
- Console.WriteLine($" AcBinary NoRef: {acNoRef.Length,8:N0} bytes ({100.0 * acNoRef.Length / msgPack.Length,5:F1}% of MsgPack)");
+ Console.WriteLine($" AcBinary WithRef: {acWithRef.Length,8:N0} bytes ({100.0 * acWithRef.Length / Math.Max(1, msgPack.Length),5:F1}% of MsgPack)");
+ Console.WriteLine($" AcBinary NoRef: {acNoRef.Length,8:N0} bytes ({100.0 * acNoRef.Length / Math.Max(1, msgPack.Length),5:F1}% of MsgPack)");
Console.WriteLine($" MessagePack: {msgPack.Length,8:N0} bytes (100.0%)");
+ Console.WriteLine($" BSON: {bson.Length,8:N0} bytes (compared to MsgPack)");
var withRefSaving = msgPack.Length - acWithRef.Length;
var noRefSaving = msgPack.Length - acNoRef.Length;
diff --git a/AyCode.Core/Extensions/AcBinaryDeserializer.cs b/AyCode.Core/Extensions/AcBinaryDeserializer.cs
index 83b5cc1..07172cf 100644
--- a/AyCode.Core/Extensions/AcBinaryDeserializer.cs
+++ b/AyCode.Core/Extensions/AcBinaryDeserializer.cs
@@ -42,48 +42,49 @@ public class AcBinaryDeserializationException : Exception
public static class AcBinaryDeserializer
{
private static readonly ConcurrentDictionary TypeMetadataCache = new();
+ private static readonly ConcurrentDictionary TypeConversionCache = new();
// Type dispatch table for fast ReadValue
private delegate object? TypeReader(ref BinaryDeserializationContext context, Type targetType, int depth);
- private static readonly FrozenDictionary TypeReaders;
+
+ private static readonly TypeReader?[] TypeReaders = new TypeReader[byte.MaxValue + 1];
+ private static readonly Encoding Utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
static AcBinaryDeserializer()
{
- // Initialize type reader dispatch table
- var readers = new Dictionary
- {
- [BinaryTypeCode.Null] = static (ref BinaryDeserializationContext _, Type _, int _) => null,
- [BinaryTypeCode.True] = static (ref BinaryDeserializationContext _, Type _, int _) => true,
- [BinaryTypeCode.False] = static (ref BinaryDeserializationContext _, Type _, int _) => false,
- [BinaryTypeCode.Int8] = static (ref BinaryDeserializationContext ctx, Type _, int _) => (sbyte)ctx.ReadByte(),
- [BinaryTypeCode.UInt8] = static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadByte(),
- [BinaryTypeCode.Int16] = static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadInt16Unsafe(),
- [BinaryTypeCode.UInt16] = static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadUInt16Unsafe(),
- [BinaryTypeCode.Int32] = static (ref BinaryDeserializationContext ctx, Type type, int _) => ReadInt32Value(ref ctx, type),
- [BinaryTypeCode.UInt32] = static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadVarUInt(),
- [BinaryTypeCode.Int64] = static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadVarLong(),
- [BinaryTypeCode.UInt64] = static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadVarULong(),
- [BinaryTypeCode.Float32] = static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadSingleUnsafe(),
- [BinaryTypeCode.Float64] = static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadDoubleUnsafe(),
- [BinaryTypeCode.Decimal] = static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadDecimalUnsafe(),
- [BinaryTypeCode.Char] = static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadCharUnsafe(),
- [BinaryTypeCode.String] = static (ref BinaryDeserializationContext ctx, Type _, int _) => ReadAndInternString(ref ctx),
- [BinaryTypeCode.StringInterned] = static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.GetInternedString((int)ctx.ReadVarUInt()),
- [BinaryTypeCode.StringEmpty] = static (ref BinaryDeserializationContext _, Type _, int _) => string.Empty,
- [BinaryTypeCode.DateTime] = static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadDateTimeUnsafe(),
- [BinaryTypeCode.DateTimeOffset] = static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadDateTimeOffsetUnsafe(),
- [BinaryTypeCode.TimeSpan] = static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadTimeSpanUnsafe(),
- [BinaryTypeCode.Guid] = static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadGuidUnsafe(),
- [BinaryTypeCode.Enum] = static (ref BinaryDeserializationContext ctx, Type type, int _) => ReadEnumValue(ref ctx, type),
- [BinaryTypeCode.Object] = ReadObject,
- [BinaryTypeCode.ObjectRef] = static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.GetReferencedObject(ctx.ReadVarInt()),
- [BinaryTypeCode.Array] = ReadArray,
- [BinaryTypeCode.Dictionary] = ReadDictionary,
- [BinaryTypeCode.ByteArray] = static (ref BinaryDeserializationContext ctx, Type _, int _) => ReadByteArray(ref ctx),
- };
- TypeReaders = readers.ToFrozenDictionary();
+ RegisterReader(BinaryTypeCode.Null, static (ref BinaryDeserializationContext _, Type _, int _) => null);
+ RegisterReader(BinaryTypeCode.True, static (ref BinaryDeserializationContext _, Type _, int _) => true);
+ RegisterReader(BinaryTypeCode.False, static (ref BinaryDeserializationContext _, Type _, int _) => false);
+ RegisterReader(BinaryTypeCode.Int8, static (ref BinaryDeserializationContext ctx, Type _, int _) => (sbyte)ctx.ReadByte());
+ RegisterReader(BinaryTypeCode.UInt8, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadByte());
+ RegisterReader(BinaryTypeCode.Int16, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadInt16Unsafe());
+ RegisterReader(BinaryTypeCode.UInt16, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadUInt16Unsafe());
+ RegisterReader(BinaryTypeCode.Int32, static (ref BinaryDeserializationContext ctx, Type type, int _) => ReadInt32Value(ref ctx, type));
+ RegisterReader(BinaryTypeCode.UInt32, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadVarUInt());
+ RegisterReader(BinaryTypeCode.Int64, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadVarLong());
+ RegisterReader(BinaryTypeCode.UInt64, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadVarULong());
+ RegisterReader(BinaryTypeCode.Float32, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadSingleUnsafe());
+ RegisterReader(BinaryTypeCode.Float64, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadDoubleUnsafe());
+ RegisterReader(BinaryTypeCode.Decimal, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadDecimalUnsafe());
+ RegisterReader(BinaryTypeCode.Char, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadCharUnsafe());
+ RegisterReader(BinaryTypeCode.String, static (ref BinaryDeserializationContext ctx, Type _, int _) => ReadAndInternString(ref ctx));
+ RegisterReader(BinaryTypeCode.StringInterned, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.GetInternedString((int)ctx.ReadVarUInt()));
+ RegisterReader(BinaryTypeCode.StringEmpty, static (ref BinaryDeserializationContext _, Type _, int _) => string.Empty);
+ RegisterReader(BinaryTypeCode.DateTime, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadDateTimeUnsafe());
+ RegisterReader(BinaryTypeCode.DateTimeOffset, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadDateTimeOffsetUnsafe());
+ RegisterReader(BinaryTypeCode.TimeSpan, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadTimeSpanUnsafe());
+ RegisterReader(BinaryTypeCode.Guid, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadGuidUnsafe());
+ RegisterReader(BinaryTypeCode.Enum, static (ref BinaryDeserializationContext ctx, Type type, int _) => ReadEnumValue(ref ctx, type));
+ RegisterReader(BinaryTypeCode.Object, ReadObject);
+ RegisterReader(BinaryTypeCode.ObjectRef, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.GetReferencedObject(ctx.ReadVarInt()));
+ RegisterReader(BinaryTypeCode.Array, ReadArray);
+ RegisterReader(BinaryTypeCode.Dictionary, ReadDictionary);
+ RegisterReader(BinaryTypeCode.ByteArray, static (ref BinaryDeserializationContext ctx, Type _, int _) => ReadByteArray(ref ctx));
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void RegisterReader(byte typeCode, TypeReader reader) => TypeReaders[typeCode] = reader;
+
#region Public API
///
@@ -269,8 +270,8 @@ public static class AcBinaryDeserializer
return ConvertToTargetType(intValue, targetType);
}
- // Use dispatch table for type-specific reading
- if (TypeReaders.TryGetValue(typeCode, out var reader))
+ var reader = TypeReaders[typeCode];
+ if (reader != null)
{
return reader(ref context, targetType, depth);
}
@@ -294,6 +295,7 @@ public static class AcBinaryDeserializer
{
context.RegisterInternedString(str);
}
+
return str;
}
@@ -307,12 +309,11 @@ public static class AcBinaryDeserializer
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static object ConvertToTargetType(int value, Type targetType)
{
- var underlying = Nullable.GetUnderlyingType(targetType) ?? targetType;
- if (underlying.IsEnum)
- return Enum.ToObject(underlying, value);
-
- var typeCode = Type.GetTypeCode(underlying);
- return typeCode switch
+ var info = GetConversionInfo(targetType);
+ if (info.IsEnum)
+ return Enum.ToObject(info.UnderlyingType, value);
+
+ return info.TypeCode switch
{
TypeCode.Int32 => value,
TypeCode.Int64 => (long)value,
@@ -329,12 +330,20 @@ public static class AcBinaryDeserializer
};
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static TypeConversionInfo GetConversionInfo(Type targetType)
+ => TypeConversionCache.GetOrAdd(targetType, static type =>
+ {
+ var underlying = Nullable.GetUnderlyingType(type) ?? type;
+ return new TypeConversionInfo(underlying, Type.GetTypeCode(underlying), underlying.IsEnum);
+ });
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static object ReadEnumValue(ref BinaryDeserializationContext context, Type targetType)
{
- var underlying = Nullable.GetUnderlyingType(targetType) ?? targetType;
+ var info = GetConversionInfo(targetType);
var nextByte = context.ReadByte();
-
+
int intValue;
if (BinaryTypeCode.IsTinyInt(nextByte))
{
@@ -351,9 +360,7 @@ public static class AcBinaryDeserializer
context.Position, targetType);
}
- if (underlying.IsEnum)
- return Enum.ToObject(underlying, intValue);
- return intValue;
+ return info.IsEnum ? Enum.ToObject(info.UnderlyingType, intValue) : intValue;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -400,7 +407,7 @@ public static class AcBinaryDeserializer
private static void PopulateObject(ref BinaryDeserializationContext context, object target, Type targetType, int depth)
{
var metadata = GetTypeMetadata(targetType);
-
+
// Skip ref ID if present
if (context.HasReferenceHandling)
{
@@ -459,7 +466,7 @@ public static class AcBinaryDeserializer
// OPTIMIZATION: Reuse existing nested objects instead of creating new ones
var peekCode = context.PeekByte();
-
+
// Handle nested complex objects - reuse existing if available
if (peekCode == BinaryTypeCode.Object && propInfo.IsComplexType)
{
@@ -471,7 +478,7 @@ public static class AcBinaryDeserializer
continue;
}
}
-
+
// Handle collections - reuse existing collection and populate items
if (peekCode == BinaryTypeCode.Array && propInfo.IsCollection)
{
@@ -734,7 +741,7 @@ public static class AcBinaryDeserializer
if (existingItem != null)
{
context.ReadByte(); // consume Object marker
-
+
// Handle ref ID if present
if (context.HasReferenceHandling)
{
@@ -744,7 +751,7 @@ public static class AcBinaryDeserializer
context.RegisterObject(refId, existingItem);
}
}
-
+
PopulateObject(ref context, existingItem, elementMetadata, nextDepth);
continue;
}
@@ -752,7 +759,7 @@ public static class AcBinaryDeserializer
// Read new value
var value = ReadValue(ref context, elementType, nextDepth);
-
+
if (i < existingCount)
{
// Replace existing item
@@ -776,6 +783,7 @@ public static class AcBinaryDeserializer
acObservable?.EndUpdate();
}
}
+
#endregion
#region Array Reading
@@ -803,6 +811,7 @@ public static class AcBinaryDeserializer
var value = ReadValue(ref context, elementType, nextDepth);
array.SetValue(value, i);
}
+
return array;
}
@@ -812,7 +821,10 @@ public static class AcBinaryDeserializer
var instance = Activator.CreateInstance(targetType);
if (instance is IList l) list = l;
}
- catch { /* Fallback to List */ }
+ catch
+ {
+ /* Fallback to List */
+ }
list ??= GetOrCreateListFactory(elementType)();
@@ -855,6 +867,7 @@ public static class AcBinaryDeserializer
else
return null; // Fall back to generic path
}
+
return array;
}
@@ -868,6 +881,7 @@ public static class AcBinaryDeserializer
if (typeCode != BinaryTypeCode.Float64) return null;
array[i] = context.ReadDoubleUnsafe();
}
+
return array;
}
@@ -887,6 +901,7 @@ public static class AcBinaryDeserializer
else
return null;
}
+
return array;
}
@@ -901,6 +916,7 @@ public static class AcBinaryDeserializer
else if (typeCode == BinaryTypeCode.False) array[i] = false;
else return null;
}
+
return array;
}
@@ -914,6 +930,7 @@ public static class AcBinaryDeserializer
if (typeCode != BinaryTypeCode.Guid) return null;
array[i] = context.ReadGuidUnsafe();
}
+
return array;
}
@@ -927,6 +944,7 @@ public static class AcBinaryDeserializer
if (typeCode != BinaryTypeCode.Decimal) return null;
array[i] = context.ReadDecimalUnsafe();
}
+
return array;
}
@@ -940,6 +958,7 @@ public static class AcBinaryDeserializer
if (typeCode != BinaryTypeCode.DateTime) return null;
array[i] = context.ReadDateTimeUnsafe();
}
+
return array;
}
@@ -995,7 +1014,7 @@ public static class AcBinaryDeserializer
var idGetter = CreateCompiledGetter(elementType, idProp);
var propInfo = new BinaryPropertySetterInfo(
"Items", elementType, true, elementType, idType, idGetter);
-
+
MergeIIdCollection(ref context, targetList, propInfo, depth);
}
@@ -1128,7 +1147,7 @@ public static class AcBinaryDeserializer
{
var byteLen = (int)context.ReadVarUInt();
if (byteLen == 0) return;
-
+
var str = context.ReadStringUtf8(byteLen);
if (str.Length >= context.MinStringInternLength)
{
@@ -1167,6 +1186,7 @@ public static class AcBinaryDeserializer
}
// StringEmpty doesn't need any action
}
+
// Skip value
SkipValue(ref context);
}
@@ -1228,7 +1248,7 @@ public static class AcBinaryDeserializer
internal sealed class BinaryDeserializeTypeMetadata
{
- private readonly Dictionary _propertiesDict;
+ private readonly FrozenDictionary _propertiesDict;
public BinaryPropertySetterInfo[] PropertiesArray { get; }
public Func