Refactor property metadata; add console perf profiler
- Introduced PropertyMetadataBase to unify property metadata and dynamic getter logic, now shared by PropertyAccessorBase and PropertySetterBase. - Moved PropertyAccessorType enum to PropertyMetadataBase. - Replaced all GetDynamicValue usages with GetValue for consistent property access in serializers/deserializers. - Refactored PropertyAccessorBase and PropertySetterBase inheritance and responsibilities. - Added MaxStringInternLength option to AcBinarySerializerOptions for configurable string interning. - Improved collection deserialization: clear destination if source is empty. - Added AyCode.Core.Serializers.Console project for performance profiling (with MessagePack comparison). - Updated solution file to include new project and build configs. - Minor code cleanups and documentation improvements.
This commit is contained in:
parent
75823d593b
commit
905b1c404d
|
|
@ -0,0 +1,19 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\AyCode.Core\AyCode.Core.csproj" />
|
||||||
|
<ProjectReference Include="..\AyCode.Core.Tests\AyCode.Core.Tests.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="MessagePack" Version="3.1.4" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,195 @@
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using AyCode.Core.Serializers.Binaries;
|
||||||
|
using AyCode.Core.Tests.TestModels;
|
||||||
|
using MessagePack;
|
||||||
|
using MessagePack.Resolvers;
|
||||||
|
|
||||||
|
namespace AyCode.Core.Serializers.Console;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Console application for Performance Diagnostics profiling.
|
||||||
|
/// Run with: Debug > Performance Profiler in Visual Studio
|
||||||
|
///
|
||||||
|
/// Usage:
|
||||||
|
/// dotnet run -- serialize # Profile serialize only
|
||||||
|
/// dotnet run -- deserialize # Profile deserialize only
|
||||||
|
/// dotnet run -- all # Profile both (default)
|
||||||
|
/// </summary>
|
||||||
|
public static class Program
|
||||||
|
{
|
||||||
|
private const int WarmupIterations = 50;
|
||||||
|
private const int TestIterations = 5000;
|
||||||
|
|
||||||
|
// Keep references to prevent GC during profiling
|
||||||
|
private static TestOrder s_testOrder = null!;
|
||||||
|
private static byte[] s_acBinaryData = null!;
|
||||||
|
private static byte[] s_acBinaryNoRefData = null!;
|
||||||
|
private static byte[] s_msgPackData = null!;
|
||||||
|
private static AcBinarySerializerOptions s_acBinaryOptions = null!;
|
||||||
|
private static AcBinarySerializerOptions s_acBinaryNoRefOptions = null!;
|
||||||
|
private static MessagePackSerializerOptions s_msgPackOptions = null!;
|
||||||
|
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
var mode = args.Length > 0 ? args[0].ToLower() : "all";
|
||||||
|
|
||||||
|
System.Console.WriteLine("=".PadRight(60, '='));
|
||||||
|
System.Console.WriteLine($"AcBinary Performance Profiler - Mode: {mode}");
|
||||||
|
System.Console.WriteLine("=".PadRight(60, '='));
|
||||||
|
|
||||||
|
Setup();
|
||||||
|
Warmup();
|
||||||
|
|
||||||
|
System.Console.WriteLine($"\nRunning {TestIterations} iterations...\n");
|
||||||
|
|
||||||
|
var sw = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
switch (mode)
|
||||||
|
{
|
||||||
|
case "serialize":
|
||||||
|
case "ser":
|
||||||
|
RunSerializeTests();
|
||||||
|
break;
|
||||||
|
case "deserialize":
|
||||||
|
case "des":
|
||||||
|
RunDeserializeTests();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
RunSerializeTests();
|
||||||
|
RunDeserializeTests();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
sw.Stop();
|
||||||
|
System.Console.WriteLine($"\nTotal time: {sw.ElapsedMilliseconds:N0} ms");
|
||||||
|
System.Console.WriteLine("=".PadRight(60, '='));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Setup()
|
||||||
|
{
|
||||||
|
System.Console.WriteLine("Creating test data...");
|
||||||
|
TestDataFactory.ResetIdCounter();
|
||||||
|
var sharedTag = TestDataFactory.CreateTag("SharedTag");
|
||||||
|
var sharedUser = TestDataFactory.CreateUser("shareduser");
|
||||||
|
var sharedMeta = TestDataFactory.CreateMetadata("shared", withChild: true);
|
||||||
|
|
||||||
|
s_testOrder = TestDataFactory.CreateOrder(
|
||||||
|
itemCount: 3,
|
||||||
|
palletsPerItem: 3,
|
||||||
|
measurementsPerPallet: 3,
|
||||||
|
pointsPerMeasurement: 4,
|
||||||
|
sharedTag: sharedTag,
|
||||||
|
sharedUser: sharedUser,
|
||||||
|
sharedMetadata: sharedMeta);
|
||||||
|
|
||||||
|
s_acBinaryOptions = AcBinarySerializerOptions.Default;
|
||||||
|
s_acBinaryNoRefOptions = AcBinarySerializerOptions.WithoutReferenceHandling();
|
||||||
|
s_msgPackOptions = ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None);
|
||||||
|
|
||||||
|
s_acBinaryData = AcBinarySerializer.Serialize(s_testOrder, s_acBinaryOptions);
|
||||||
|
s_acBinaryNoRefData = AcBinarySerializer.Serialize(s_testOrder, s_acBinaryNoRefOptions);
|
||||||
|
s_msgPackData = MessagePackSerializer.Serialize(s_testOrder, s_msgPackOptions);
|
||||||
|
|
||||||
|
System.Console.WriteLine($" AcBinary (WithRef): {s_acBinaryData.Length:N0} bytes");
|
||||||
|
System.Console.WriteLine($" AcBinary (NoRef): {s_acBinaryNoRefData.Length:N0} bytes");
|
||||||
|
System.Console.WriteLine($" MessagePack: {s_msgPackData.Length:N0} bytes");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Warmup()
|
||||||
|
{
|
||||||
|
System.Console.WriteLine($"Warming up ({WarmupIterations} iterations)...");
|
||||||
|
for (int i = 0; i < WarmupIterations; i++)
|
||||||
|
{
|
||||||
|
DoSerializeAcBinary();
|
||||||
|
DoSerializeAcBinaryNoRef();
|
||||||
|
DoSerializeMsgPack();
|
||||||
|
DoDeserializeAcBinary();
|
||||||
|
DoDeserializeAcBinaryNoRef();
|
||||||
|
DoDeserializeMsgPack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RunSerializeTests()
|
||||||
|
{
|
||||||
|
System.Console.WriteLine("--- SERIALIZE ---");
|
||||||
|
|
||||||
|
var sw = Stopwatch.StartNew();
|
||||||
|
for (int i = 0; i < TestIterations; i++)
|
||||||
|
{
|
||||||
|
DoSerializeAcBinary();
|
||||||
|
}
|
||||||
|
sw.Stop();
|
||||||
|
System.Console.WriteLine($" AcBinary (WithRef): {sw.ElapsedMilliseconds,6:N0} ms");
|
||||||
|
|
||||||
|
sw.Restart();
|
||||||
|
for (int i = 0; i < TestIterations; i++)
|
||||||
|
{
|
||||||
|
DoSerializeAcBinaryNoRef();
|
||||||
|
}
|
||||||
|
sw.Stop();
|
||||||
|
System.Console.WriteLine($" AcBinary (NoRef): {sw.ElapsedMilliseconds,6:N0} ms");
|
||||||
|
|
||||||
|
sw.Restart();
|
||||||
|
for (int i = 0; i < TestIterations; i++)
|
||||||
|
{
|
||||||
|
DoSerializeMsgPack();
|
||||||
|
}
|
||||||
|
sw.Stop();
|
||||||
|
System.Console.WriteLine($" MessagePack: {sw.ElapsedMilliseconds,6:N0} ms");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RunDeserializeTests()
|
||||||
|
{
|
||||||
|
System.Console.WriteLine("--- DESERIALIZE ---");
|
||||||
|
|
||||||
|
var sw = Stopwatch.StartNew();
|
||||||
|
for (int i = 0; i < TestIterations; i++)
|
||||||
|
{
|
||||||
|
DoDeserializeAcBinary();
|
||||||
|
}
|
||||||
|
sw.Stop();
|
||||||
|
System.Console.WriteLine($" AcBinary (WithRef): {sw.ElapsedMilliseconds,6:N0} ms");
|
||||||
|
|
||||||
|
sw.Restart();
|
||||||
|
for (int i = 0; i < TestIterations; i++)
|
||||||
|
{
|
||||||
|
DoDeserializeAcBinaryNoRef();
|
||||||
|
}
|
||||||
|
sw.Stop();
|
||||||
|
System.Console.WriteLine($" AcBinary (NoRef): {sw.ElapsedMilliseconds,6:N0} ms");
|
||||||
|
|
||||||
|
sw.Restart();
|
||||||
|
for (int i = 0; i < TestIterations; i++)
|
||||||
|
{
|
||||||
|
DoDeserializeMsgPack();
|
||||||
|
}
|
||||||
|
sw.Stop();
|
||||||
|
System.Console.WriteLine($" MessagePack: {sw.ElapsedMilliseconds,6:N0} ms");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Separate methods for better profiler visibility - NO INLINING
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
private static byte[] DoSerializeAcBinary()
|
||||||
|
=> AcBinarySerializer.Serialize(s_testOrder, s_acBinaryOptions);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
private static byte[] DoSerializeAcBinaryNoRef()
|
||||||
|
=> AcBinarySerializer.Serialize(s_testOrder, s_acBinaryNoRefOptions);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
private static byte[] DoSerializeMsgPack()
|
||||||
|
=> MessagePackSerializer.Serialize(s_testOrder, s_msgPackOptions);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
private static TestOrder? DoDeserializeAcBinary()
|
||||||
|
=> AcBinaryDeserializer.Deserialize<TestOrder>(s_acBinaryData);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
private static TestOrder? DoDeserializeAcBinaryNoRef()
|
||||||
|
=> AcBinaryDeserializer.Deserialize<TestOrder>(s_acBinaryNoRefData);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
private static TestOrder? DoDeserializeMsgPack()
|
||||||
|
=> MessagePackSerializer.Deserialize<TestOrder>(s_msgPackData, s_msgPackOptions);
|
||||||
|
}
|
||||||
|
|
@ -53,6 +53,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AyCode.Services.Tests", "Ay
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AyCode.Core.Serializers.SourceGenerator", "AyCode.Core.Serializers.SourceGenerator\AyCode.Core.Serializers.SourceGenerator.csproj", "{4A817897-80A8-4F42-86C5-20447401E0AA}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AyCode.Core.Serializers.SourceGenerator", "AyCode.Core.Serializers.SourceGenerator\AyCode.Core.Serializers.SourceGenerator.csproj", "{4A817897-80A8-4F42-86C5-20447401E0AA}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AyCode.Core.Serializers.Console", "AyCode.Core.Serializers.Console\AyCode.Core.Serializers.Console.csproj", "{6AB7CE43-3C98-1D54-9ABD-E5E9364541E7}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
|
@ -433,6 +435,24 @@ Global
|
||||||
{4A817897-80A8-4F42-86C5-20447401E0AA}.Release|x64.Build.0 = Release|Any CPU
|
{4A817897-80A8-4F42-86C5-20447401E0AA}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{4A817897-80A8-4F42-86C5-20447401E0AA}.Release|x86.ActiveCfg = Release|Any CPU
|
{4A817897-80A8-4F42-86C5-20447401E0AA}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{4A817897-80A8-4F42-86C5-20447401E0AA}.Release|x86.Build.0 = Release|Any CPU
|
{4A817897-80A8-4F42-86C5-20447401E0AA}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{6AB7CE43-3C98-1D54-9ABD-E5E9364541E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{6AB7CE43-3C98-1D54-9ABD-E5E9364541E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{6AB7CE43-3C98-1D54-9ABD-E5E9364541E7}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{6AB7CE43-3C98-1D54-9ABD-E5E9364541E7}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{6AB7CE43-3C98-1D54-9ABD-E5E9364541E7}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{6AB7CE43-3C98-1D54-9ABD-E5E9364541E7}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{6AB7CE43-3C98-1D54-9ABD-E5E9364541E7}.Product|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{6AB7CE43-3C98-1D54-9ABD-E5E9364541E7}.Product|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{6AB7CE43-3C98-1D54-9ABD-E5E9364541E7}.Product|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{6AB7CE43-3C98-1D54-9ABD-E5E9364541E7}.Product|x64.Build.0 = Release|Any CPU
|
||||||
|
{6AB7CE43-3C98-1D54-9ABD-E5E9364541E7}.Product|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{6AB7CE43-3C98-1D54-9ABD-E5E9364541E7}.Product|x86.Build.0 = Release|Any CPU
|
||||||
|
{6AB7CE43-3C98-1D54-9ABD-E5E9364541E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{6AB7CE43-3C98-1D54-9ABD-E5E9364541E7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{6AB7CE43-3C98-1D54-9ABD-E5E9364541E7}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{6AB7CE43-3C98-1D54-9ABD-E5E9364541E7}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{6AB7CE43-3C98-1D54-9ABD-E5E9364541E7}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{6AB7CE43-3C98-1D54-9ABD-E5E9364541E7}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,7 @@ public static partial class AcBinaryDeserializer
|
||||||
public new bool IsIIdCollection => _isManualConstruction ? _manualIsIIdCollection : base.IsIIdCollection;
|
public new bool IsIIdCollection => _isManualConstruction ? _manualIsIIdCollection : base.IsIIdCollection;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public new object? GetValue(object target) => _isManualConstruction ? _manualGetter!(target) : base.GetDynamicValue(target);
|
public new object? GetValue(object target) => _isManualConstruction ? _manualGetter!(target) : base.GetValue(target);
|
||||||
|
|
||||||
public override void SetValue(object target, object? value)
|
public override void SetValue(object target, object? value)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -110,10 +110,10 @@ public static partial class AcBinaryDeserializer
|
||||||
var existingObj = propInfo.GetValue(target);
|
var existingObj = propInfo.GetValue(target);
|
||||||
if (existingObj != null)
|
if (existingObj != null)
|
||||||
{
|
{
|
||||||
var wrapper = context.ContextClass.GetWrapper(propInfo.PropertyType);
|
|
||||||
|
|
||||||
context.ReadByte(); // consume Object marker
|
context.ReadByte(); // consume Object marker
|
||||||
|
|
||||||
|
var wrapper = context.ContextClass.GetWrapper(propInfo.PropertyType);
|
||||||
|
|
||||||
// Handle ref ID if present
|
// Handle ref ID if present
|
||||||
if (context.HasReferenceHandling)
|
if (context.HasReferenceHandling)
|
||||||
{
|
{
|
||||||
|
|
@ -135,7 +135,9 @@ public static partial class AcBinaryDeserializer
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Use typed setters for primitives to avoid boxing
|
// Use typed setters for primitives to avoid boxing
|
||||||
if (TryReadAndSetTypedValue(ref context, target, propInfo, peekCode))
|
// Skip method call for Object/String/Collection types - they can't use typed setters
|
||||||
|
if (propInfo.AccessorType != PropertyAccessorType.Object &&
|
||||||
|
TryReadAndSetTypedValue(ref context, target, propInfo, peekCode))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var value = ReadValue(ref context, propInfo.PropertyType, nextDepth);
|
var value = ReadValue(ref context, propInfo.PropertyType, nextDepth);
|
||||||
|
|
@ -214,9 +216,16 @@ public static partial class AcBinaryDeserializer
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var wrapper = context.ContextClass.GetWrapper(elementType);
|
|
||||||
var existingCount = existingList.Count;
|
var existingCount = existingList.Count;
|
||||||
|
|
||||||
|
// Early exit if empty source - just clear destination
|
||||||
|
if (count == 0)
|
||||||
|
{
|
||||||
|
existingList.Clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var wrapper = context.ContextClass.GetWrapper(elementType);
|
||||||
var elementMetadata = wrapper.Metadata.IsComplexType ? wrapper.Metadata : null;
|
var elementMetadata = wrapper.Metadata.IsComplexType ? wrapper.Metadata : null;
|
||||||
|
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,7 @@ public static partial class AcBinarySerializer
|
||||||
public bool UseMetadata { get; private set; }
|
public bool UseMetadata { get; private set; }
|
||||||
public byte MaxDepth { get; private set; }
|
public byte MaxDepth { get; private set; }
|
||||||
public byte MinStringInternLength { get; private set; }
|
public byte MinStringInternLength { get; private set; }
|
||||||
|
public byte MaxStringInternLength { get; private set; }
|
||||||
public BinaryPropertyFilter? PropertyFilter { get; private set; }
|
public BinaryPropertyFilter? PropertyFilter { get; private set; }
|
||||||
|
|
||||||
public int Position => _position;
|
public int Position => _position;
|
||||||
|
|
@ -114,6 +115,7 @@ public static partial class AcBinarySerializer
|
||||||
UseMetadata = options.UseMetadata;
|
UseMetadata = options.UseMetadata;
|
||||||
MaxDepth = options.MaxDepth;
|
MaxDepth = options.MaxDepth;
|
||||||
MinStringInternLength = options.MinStringInternLength;
|
MinStringInternLength = options.MinStringInternLength;
|
||||||
|
MaxStringInternLength = options.MaxStringInternLength;
|
||||||
PropertyFilter = options.PropertyFilter;
|
PropertyFilter = options.PropertyFilter;
|
||||||
_initialBufferSize = Math.Max(options.InitialBufferCapacity, MinBufferSize);
|
_initialBufferSize = Math.Max(options.InitialBufferCapacity, MinBufferSize);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
using AyCode.Core.Helpers;
|
using AyCode.Core.Helpers;
|
||||||
using AyCode.Core.Serializers.Expressions;
|
using AyCode.Core.Serializers.Expressions;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
|
@ -597,7 +597,11 @@ public static partial class AcBinarySerializer
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.UseStringInterning && value.Length >= context.MinStringInternLength)
|
// String interning: only for strings within length range
|
||||||
|
// MaxStringInternLength == 0 means no max limit
|
||||||
|
if (context.UseStringInterning
|
||||||
|
&& value.Length >= context.MinStringInternLength
|
||||||
|
&& (context.MaxStringInternLength == 0 || value.Length <= context.MaxStringInternLength))
|
||||||
{
|
{
|
||||||
var index = context.RegisterInternedString(value);
|
var index = context.RegisterInternedString(value);
|
||||||
context.WriteByte(BinaryTypeCode.StringInterned);
|
context.WriteByte(BinaryTypeCode.StringInterned);
|
||||||
|
|
@ -642,12 +646,12 @@ public static partial class AcBinarySerializer
|
||||||
case AcSerializerCommon.IdAccessorType.Int32:
|
case AcSerializerCommon.IdAccessorType.Int32:
|
||||||
if (!context.TryTrack(wrapper, value, out int intId))
|
if (!context.TryTrack(wrapper, value, out int intId))
|
||||||
{
|
{
|
||||||
// Already seen → write reference
|
// Already seen › write reference
|
||||||
context.WriteByte(BinaryTypeCode.ObjectRef);
|
context.WriteByte(BinaryTypeCode.ObjectRef);
|
||||||
context.WriteVarInt(intId);
|
context.WriteVarInt(intId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// First occurrence → write object with refId
|
// First occurrence › write object with refId
|
||||||
context.WriteByte(BinaryTypeCode.Object);
|
context.WriteByte(BinaryTypeCode.Object);
|
||||||
context.WriteVarInt(intId);
|
context.WriteVarInt(intId);
|
||||||
break;
|
break;
|
||||||
|
|
@ -747,7 +751,7 @@ public static partial class AcBinarySerializer
|
||||||
return false;
|
return false;
|
||||||
default:
|
default:
|
||||||
// Object type - use regular getter
|
// Object type - use regular getter
|
||||||
var value = prop.GetDynamicValue(obj);
|
var value = prop.GetValue(obj);
|
||||||
if (value == null) return true;
|
if (value == null) return true;
|
||||||
if (prop.PropertyTypeCode == TypeCode.String) return string.IsNullOrEmpty((string)value);
|
if (prop.PropertyTypeCode == TypeCode.String) return string.IsNullOrEmpty((string)value);
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -818,7 +822,7 @@ public static partial class AcBinarySerializer
|
||||||
return;
|
return;
|
||||||
default:
|
default:
|
||||||
// Fallback to object getter for reference types
|
// Fallback to object getter for reference types
|
||||||
var value = prop.GetDynamicValue(obj);
|
var value = prop.GetValue(obj);
|
||||||
WriteValue(value, prop.PropertyType, context, depth);
|
WriteValue(value, prop.PropertyType, context, depth);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -975,7 +979,7 @@ public static partial class AcBinarySerializer
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
// Object type - use regular getter
|
// Object type - use regular getter
|
||||||
var value = prop.GetDynamicValue(obj);
|
var value = prop.GetValue(obj);
|
||||||
|
|
||||||
// SKIP marker only for null (reference types)
|
// SKIP marker only for null (reference types)
|
||||||
// Empty string, empty collections, etc. are valid values and must be written!
|
// Empty string, empty collections, etc. are valid values and must be written!
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,14 @@ public sealed class AcBinarySerializerOptions : AcSerializerOptions
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public byte MinStringInternLength { get; init; } = 4;
|
public byte MinStringInternLength { get; init; } = 4;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum string length to consider for interning.
|
||||||
|
/// Longer strings (descriptions, notes, etc.) are usually unique and not worth interning.
|
||||||
|
/// Set to 0 to disable max limit.
|
||||||
|
/// Default: 64 (strings longer than 64 chars are not interned)
|
||||||
|
/// </summary>
|
||||||
|
public byte MaxStringInternLength { get; init; } = 64;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initial capacity for serialization buffer.
|
/// Initial capacity for serialization buffer.
|
||||||
/// Default: 4096 bytes
|
/// Default: 4096 bytes
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,7 @@ public static partial class AcJsonDeserializer
|
||||||
for (var i = 0; i < props.Length; i++)
|
for (var i = 0; i < props.Length; i++)
|
||||||
{
|
{
|
||||||
var prop = props[i];
|
var prop = props[i];
|
||||||
var value = prop.GetDynamicValue(source);
|
var value = prop.GetValue(source);
|
||||||
if (value != null)
|
if (value != null)
|
||||||
prop.SetValue(target, value);
|
prop.SetValue(target, value);
|
||||||
}
|
}
|
||||||
|
|
@ -176,7 +176,7 @@ public static partial class AcJsonDeserializer
|
||||||
// Handle IId collection merge
|
// Handle IId collection merge
|
||||||
if (propInfo.IsIIdCollection && propValueKind == JsonValueKind.Array)
|
if (propInfo.IsIIdCollection && propValueKind == JsonValueKind.Array)
|
||||||
{
|
{
|
||||||
var existingCollection = propInfo.GetDynamicValue(target);
|
var existingCollection = propInfo.GetValue(target);
|
||||||
if (existingCollection != null)
|
if (existingCollection != null)
|
||||||
{
|
{
|
||||||
MergeIIdCollection(propValue, existingCollection, propInfo, context, depth);
|
MergeIIdCollection(propValue, existingCollection, propInfo, context, depth);
|
||||||
|
|
@ -201,7 +201,7 @@ public static partial class AcJsonDeserializer
|
||||||
// Merge into existing object
|
// Merge into existing object
|
||||||
if (!propInfo.PropertyType.IsPrimitive && !ReferenceEquals(propInfo.PropertyType, StringType))
|
if (!propInfo.PropertyType.IsPrimitive && !ReferenceEquals(propInfo.PropertyType, StringType))
|
||||||
{
|
{
|
||||||
var existingObj = propInfo.GetDynamicValue(target);
|
var existingObj = propInfo.GetValue(target);
|
||||||
if (existingObj != null)
|
if (existingObj != null)
|
||||||
{
|
{
|
||||||
var nestedMetadata = GetTypeMetadata(propInfo.PropertyType);
|
var nestedMetadata = GetTypeMetadata(propInfo.PropertyType);
|
||||||
|
|
|
||||||
|
|
@ -489,7 +489,7 @@ public static partial class AcJsonDeserializer
|
||||||
// Handle IId collection merge
|
// Handle IId collection merge
|
||||||
if (propInfo.IsIIdCollection && tokenType == JsonTokenType.StartArray)
|
if (propInfo.IsIIdCollection && tokenType == JsonTokenType.StartArray)
|
||||||
{
|
{
|
||||||
var existingCollection = propInfo.GetDynamicValue(target);
|
var existingCollection = propInfo.GetValue(target);
|
||||||
if (existingCollection != null)
|
if (existingCollection != null)
|
||||||
{
|
{
|
||||||
MergeIIdCollectionFromReader(ref reader, existingCollection, propInfo, maxDepth, depth);
|
MergeIIdCollectionFromReader(ref reader, existingCollection, propInfo, maxDepth, depth);
|
||||||
|
|
@ -500,7 +500,7 @@ public static partial class AcJsonDeserializer
|
||||||
// Handle nested objects - merge into existing
|
// Handle nested objects - merge into existing
|
||||||
if (tokenType == JsonTokenType.StartObject && !propInfo.PropertyType.IsPrimitive && !ReferenceEquals(propInfo.PropertyType, StringType))
|
if (tokenType == JsonTokenType.StartObject && !propInfo.PropertyType.IsPrimitive && !ReferenceEquals(propInfo.PropertyType, StringType))
|
||||||
{
|
{
|
||||||
var existingObj = propInfo.GetDynamicValue(target);
|
var existingObj = propInfo.GetValue(target);
|
||||||
if (existingObj != null)
|
if (existingObj != null)
|
||||||
{
|
{
|
||||||
var nestedMetadata = GetTypeMetadata(propInfo.PropertyType);
|
var nestedMetadata = GetTypeMetadata(propInfo.PropertyType);
|
||||||
|
|
@ -611,7 +611,7 @@ public static partial class AcJsonDeserializer
|
||||||
{
|
{
|
||||||
foreach (var prop in metadata.PropertySettersFrozen.Values)
|
foreach (var prop in metadata.PropertySettersFrozen.Values)
|
||||||
{
|
{
|
||||||
var value = prop.GetDynamicValue(source);
|
var value = prop.GetValue(source);
|
||||||
if (value != null)
|
if (value != null)
|
||||||
prop.SetValue(target, value);
|
prop.SetValue(target, value);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,7 @@ public static partial class AcJsonSerializer
|
||||||
var propCount = props.Length;
|
var propCount = props.Length;
|
||||||
for (var i = 0; i < propCount; i++)
|
for (var i = 0; i < propCount; i++)
|
||||||
{
|
{
|
||||||
var propValue = props[i].GetDynamicValue(value);
|
var propValue = props[i].GetValue(value);
|
||||||
if (propValue != null) ScanReferences(propValue, context, depth + 1);
|
if (propValue != null) ScanReferences(propValue, context, depth + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -205,7 +205,7 @@ public static partial class AcJsonSerializer
|
||||||
for (var i = 0; i < propCount; i++)
|
for (var i = 0; i < propCount; i++)
|
||||||
{
|
{
|
||||||
var prop = props[i];
|
var prop = props[i];
|
||||||
var propValue = prop.GetDynamicValue(value);
|
var propValue = prop.GetValue(value);
|
||||||
if (propValue == null) continue;
|
if (propValue == null) continue;
|
||||||
if (IsDefaultValueFast(propValue, prop.PropertyTypeCode, prop.PropertyType)) continue;
|
if (IsDefaultValueFast(propValue, prop.PropertyTypeCode, prop.PropertyType)) continue;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,90 +1,16 @@
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
|
||||||
using static AyCode.Core.Helpers.JsonUtilities;
|
using static AyCode.Core.Helpers.JsonUtilities;
|
||||||
|
|
||||||
namespace AyCode.Core.Serializers;
|
namespace AyCode.Core.Serializers;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enum for typed property accessor dispatch.
|
/// Base class for property accessors used by serializers.
|
||||||
/// </summary>
|
/// Contains getter functionality and typed delegate fields.
|
||||||
public enum PropertyAccessorType : byte
|
|
||||||
{
|
|
||||||
Object = 0,
|
|
||||||
Int32,
|
|
||||||
Int64,
|
|
||||||
Boolean,
|
|
||||||
Double,
|
|
||||||
Single,
|
|
||||||
Decimal,
|
|
||||||
DateTime,
|
|
||||||
Byte,
|
|
||||||
Int16,
|
|
||||||
UInt16,
|
|
||||||
UInt32,
|
|
||||||
UInt64,
|
|
||||||
Guid,
|
|
||||||
Enum
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Base class for property accessors used by all serializers.
|
|
||||||
/// Contains common property metadata, getter functionality, and typed delegate fields.
|
|
||||||
/// Typed getters eliminate runtime cast overhead for value type properties.
|
/// Typed getters eliminate runtime cast overhead for value type properties.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class PropertyAccessorBase
|
public abstract class PropertyAccessorBase : PropertyMetadataBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Property name.
|
|
||||||
/// </summary>
|
|
||||||
public string Name { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Pre-encoded UTF8 bytes of property name for fast matching.
|
|
||||||
/// </summary>
|
|
||||||
public byte[] NameUtf8 { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The property type (may be nullable).
|
|
||||||
/// </summary>
|
|
||||||
public Type PropertyType { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The underlying type (unwrapped from Nullable if applicable).
|
|
||||||
/// </summary>
|
|
||||||
public Type UnderlyingType { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Cached TypeCode for fast primitive type dispatch.
|
|
||||||
/// </summary>
|
|
||||||
public TypeCode PropertyTypeCode { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether the property type is nullable.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsNullable { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The declaring type of this property.
|
|
||||||
/// </summary>
|
|
||||||
public Type DeclaringType { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// True if this property needs recursive scanning (not primitive/string).
|
|
||||||
/// Pre-computed to avoid IsPrimitiveOrStringFast() calls in hot path.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsComplexType { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The accessor type for fast typed getter dispatch.
|
|
||||||
/// </summary>
|
|
||||||
public PropertyAccessorType AccessorType { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Compiled getter delegate for reading property values (boxed).
|
|
||||||
/// </summary>
|
|
||||||
protected readonly Func<object, object?> _dynamicGetter;
|
|
||||||
|
|
||||||
#region Strongly-typed getter delegate fields (eliminates runtime cast)
|
#region Strongly-typed getter delegate fields (eliminates runtime cast)
|
||||||
|
|
||||||
// Only ONE of these is set based on AccessorType
|
// Only ONE of these is set based on AccessorType
|
||||||
|
|
@ -105,57 +31,11 @@ public abstract class PropertyAccessorBase
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
protected PropertyAccessorBase(PropertyInfo prop, Type declaringType)
|
protected PropertyAccessorBase(PropertyInfo prop, Type declaringType)
|
||||||
|
: base(prop, declaringType)
|
||||||
{
|
{
|
||||||
Name = prop.Name;
|
|
||||||
NameUtf8 = Encoding.UTF8.GetBytes(prop.Name);
|
|
||||||
DeclaringType = declaringType;
|
|
||||||
PropertyType = prop.PropertyType;
|
|
||||||
|
|
||||||
var underlying = Nullable.GetUnderlyingType(PropertyType);
|
|
||||||
IsNullable = underlying != null;
|
|
||||||
UnderlyingType = underlying ?? PropertyType;
|
|
||||||
PropertyTypeCode = Type.GetTypeCode(UnderlyingType);
|
|
||||||
|
|
||||||
// Pre-compute: is this a complex type that needs recursive handling?
|
|
||||||
IsComplexType = !IsPrimitiveOrStringFast(PropertyType);
|
|
||||||
|
|
||||||
_dynamicGetter = AcSerializerCommon.CreateCompiledGetter(declaringType, prop);
|
|
||||||
|
|
||||||
// Initialize typed getter
|
|
||||||
AccessorType = DetermineAccessorType(PropertyType);
|
|
||||||
InitializeTypedGetter(declaringType, prop);
|
InitializeTypedGetter(declaringType, prop);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PropertyAccessorType DetermineAccessorType(Type propType)
|
|
||||||
{
|
|
||||||
var underlying = Nullable.GetUnderlyingType(propType);
|
|
||||||
if (underlying != null)
|
|
||||||
return PropertyAccessorType.Object;
|
|
||||||
|
|
||||||
if (propType.IsEnum)
|
|
||||||
return PropertyAccessorType.Enum;
|
|
||||||
|
|
||||||
if (ReferenceEquals(propType, GuidType))
|
|
||||||
return PropertyAccessorType.Guid;
|
|
||||||
|
|
||||||
return Type.GetTypeCode(propType) switch
|
|
||||||
{
|
|
||||||
TypeCode.Int32 => PropertyAccessorType.Int32,
|
|
||||||
TypeCode.Int64 => PropertyAccessorType.Int64,
|
|
||||||
TypeCode.Boolean => PropertyAccessorType.Boolean,
|
|
||||||
TypeCode.Double => PropertyAccessorType.Double,
|
|
||||||
TypeCode.Single => PropertyAccessorType.Single,
|
|
||||||
TypeCode.Decimal => PropertyAccessorType.Decimal,
|
|
||||||
TypeCode.DateTime => PropertyAccessorType.DateTime,
|
|
||||||
TypeCode.Byte => PropertyAccessorType.Byte,
|
|
||||||
TypeCode.Int16 => PropertyAccessorType.Int16,
|
|
||||||
TypeCode.UInt16 => PropertyAccessorType.UInt16,
|
|
||||||
TypeCode.UInt32 => PropertyAccessorType.UInt32,
|
|
||||||
TypeCode.UInt64 => PropertyAccessorType.UInt64,
|
|
||||||
_ => PropertyAccessorType.Object
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeTypedGetter(Type declaringType, PropertyInfo prop)
|
private void InitializeTypedGetter(Type declaringType, PropertyInfo prop)
|
||||||
{
|
{
|
||||||
switch (AccessorType)
|
switch (AccessorType)
|
||||||
|
|
@ -207,12 +87,6 @@ public abstract class PropertyAccessorBase
|
||||||
|
|
||||||
#region Typed Getters - Direct invocation, no cast!
|
#region Typed Getters - Direct invocation, no cast!
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the property value from the target object (boxed).
|
|
||||||
/// </summary>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public object? GetDynamicValue(object obj) => _dynamicGetter(obj);
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public int GetInt32(object obj) => _int32Getter!(obj);
|
public int GetInt32(object obj) => _int32Getter!(obj);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,143 @@
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text;
|
||||||
|
using static AyCode.Core.Helpers.JsonUtilities;
|
||||||
|
|
||||||
|
namespace AyCode.Core.Serializers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enum for typed property accessor dispatch.
|
||||||
|
/// </summary>
|
||||||
|
public enum PropertyAccessorType : byte
|
||||||
|
{
|
||||||
|
Object = 0,
|
||||||
|
Int32,
|
||||||
|
Int64,
|
||||||
|
Boolean,
|
||||||
|
Double,
|
||||||
|
Single,
|
||||||
|
Decimal,
|
||||||
|
DateTime,
|
||||||
|
Byte,
|
||||||
|
Int16,
|
||||||
|
UInt16,
|
||||||
|
UInt32,
|
||||||
|
UInt64,
|
||||||
|
Guid,
|
||||||
|
Enum
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base class containing common property metadata shared by serializers and deserializers.
|
||||||
|
/// Contains the dynamic getter used by both serialize (for reading) and deserialize (for Populate/Merge).
|
||||||
|
/// </summary>
|
||||||
|
public abstract class PropertyMetadataBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Property name.
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pre-encoded UTF8 bytes of property name for fast matching.
|
||||||
|
/// </summary>
|
||||||
|
public byte[] NameUtf8 { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The property type (may be nullable).
|
||||||
|
/// </summary>
|
||||||
|
public Type PropertyType { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The underlying type (unwrapped from Nullable if applicable).
|
||||||
|
/// </summary>
|
||||||
|
public Type UnderlyingType { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Cached TypeCode for fast primitive type dispatch.
|
||||||
|
/// </summary>
|
||||||
|
public TypeCode PropertyTypeCode { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the property type is nullable.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsNullable { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The declaring type of this property.
|
||||||
|
/// </summary>
|
||||||
|
public Type DeclaringType { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// True if this property needs recursive scanning (not primitive/string).
|
||||||
|
/// Pre-computed to avoid IsPrimitiveOrStringFast() calls in hot path.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsComplexType { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The accessor type for fast typed getter/setter dispatch.
|
||||||
|
/// </summary>
|
||||||
|
public PropertyAccessorType AccessorType { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compiled getter delegate for reading property values (boxed).
|
||||||
|
/// Used by serialize (for reading values) and deserialize (for Populate/Merge to get existing references).
|
||||||
|
/// </summary>
|
||||||
|
protected readonly Func<object, object?> _dynamicGetter;
|
||||||
|
|
||||||
|
protected PropertyMetadataBase(PropertyInfo prop, Type declaringType)
|
||||||
|
{
|
||||||
|
Name = prop.Name;
|
||||||
|
NameUtf8 = Encoding.UTF8.GetBytes(prop.Name);
|
||||||
|
DeclaringType = declaringType;
|
||||||
|
PropertyType = prop.PropertyType;
|
||||||
|
|
||||||
|
var underlying = Nullable.GetUnderlyingType(PropertyType);
|
||||||
|
IsNullable = underlying != null;
|
||||||
|
UnderlyingType = underlying ?? PropertyType;
|
||||||
|
PropertyTypeCode = Type.GetTypeCode(UnderlyingType);
|
||||||
|
|
||||||
|
// Pre-compute: is this a complex type that needs recursive handling?
|
||||||
|
IsComplexType = !IsPrimitiveOrStringFast(PropertyType);
|
||||||
|
|
||||||
|
AccessorType = DetermineAccessorType(PropertyType);
|
||||||
|
_dynamicGetter = AcSerializerCommon.CreateCompiledGetter(declaringType, prop);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the property value from the target object (boxed).
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public object? GetValue(object obj) => _dynamicGetter(obj);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
protected static PropertyAccessorType DetermineAccessorType(Type propType)
|
||||||
|
{
|
||||||
|
var underlying = Nullable.GetUnderlyingType(propType);
|
||||||
|
if (underlying != null)
|
||||||
|
return PropertyAccessorType.Object;
|
||||||
|
|
||||||
|
if (propType.IsEnum)
|
||||||
|
return PropertyAccessorType.Enum;
|
||||||
|
|
||||||
|
if (ReferenceEquals(propType, GuidType))
|
||||||
|
return PropertyAccessorType.Guid;
|
||||||
|
|
||||||
|
return Type.GetTypeCode(propType) switch
|
||||||
|
{
|
||||||
|
TypeCode.Int32 => PropertyAccessorType.Int32,
|
||||||
|
TypeCode.Int64 => PropertyAccessorType.Int64,
|
||||||
|
TypeCode.Boolean => PropertyAccessorType.Boolean,
|
||||||
|
TypeCode.Double => PropertyAccessorType.Double,
|
||||||
|
TypeCode.Single => PropertyAccessorType.Single,
|
||||||
|
TypeCode.Decimal => PropertyAccessorType.Decimal,
|
||||||
|
TypeCode.DateTime => PropertyAccessorType.DateTime,
|
||||||
|
TypeCode.Byte => PropertyAccessorType.Byte,
|
||||||
|
TypeCode.Int16 => PropertyAccessorType.Int16,
|
||||||
|
TypeCode.UInt16 => PropertyAccessorType.UInt16,
|
||||||
|
TypeCode.UInt32 => PropertyAccessorType.UInt32,
|
||||||
|
TypeCode.UInt64 => PropertyAccessorType.UInt64,
|
||||||
|
_ => PropertyAccessorType.Object
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,10 +6,12 @@ using static AyCode.Core.Helpers.JsonUtilities;
|
||||||
namespace AyCode.Core.Serializers;
|
namespace AyCode.Core.Serializers;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base class for property accessors that also support setting values.
|
/// Base class for property setters used by deserializers.
|
||||||
/// Used by deserializers. Extends PropertyAccessorBase with typed setters and IId collection support.
|
/// Derives from PropertyMetadataBase (not PropertyAccessorBase) to avoid unnecessary typed getter delegates.
|
||||||
|
/// Contains setter functionality and typed setter delegates.
|
||||||
|
/// Inherits GetValue() from PropertyMetadataBase for Populate/Merge operations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class PropertySetterBase : PropertyAccessorBase
|
public abstract class PropertySetterBase : PropertyMetadataBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Compiled setter delegate for writing property values (boxed).
|
/// Compiled setter delegate for writing property values (boxed).
|
||||||
|
|
|
||||||
|
|
@ -247,7 +247,7 @@ public static partial class AcToonSerializer
|
||||||
// Write properties
|
// Write properties
|
||||||
foreach (var prop in metadata.Properties)
|
foreach (var prop in metadata.Properties)
|
||||||
{
|
{
|
||||||
var propValue = prop.GetDynamicValue(value);
|
var propValue = prop.GetValue(value);
|
||||||
|
|
||||||
// Skip null/default values if option is set
|
// Skip null/default values if option is set
|
||||||
if (context.Options.OmitDefaultValues && prop.IsDefaultValue(propValue))
|
if (context.Options.OmitDefaultValues && prop.IsDefaultValue(propValue))
|
||||||
|
|
|
||||||
|
|
@ -313,7 +313,7 @@ public static partial class AcToonSerializer
|
||||||
var metadata = GetTypeMetadata(type);
|
var metadata = GetTypeMetadata(type);
|
||||||
foreach (var prop in metadata.Properties)
|
foreach (var prop in metadata.Properties)
|
||||||
{
|
{
|
||||||
var propValue = prop.GetDynamicValue(value);
|
var propValue = prop.GetValue(value);
|
||||||
if (propValue != null) ScanReferences(propValue, context, depth + 1);
|
if (propValue != null) ScanReferences(propValue, context, depth + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue