From 75823d593bad6b2e806b7b6e05dccdefd5acf8ec Mon Sep 17 00:00:00 2001 From: Loretta Date: Wed, 21 Jan 2026 10:36:06 +0100 Subject: [PATCH] Refactor: centralize strongly-typed property accessors Move strongly-typed getter/setter logic and PropertyAccessorType enum into PropertyAccessorBase and PropertySetterBase, eliminating duplication in binary accessor classes. Expose direct typed getter/setter methods and new SetValueTyped/SetToDefault helpers. Rename ObjectGetter to DynamicGetter and update all serializers/deserializers to use GetDynamicValue. Centralize default value logic and improve performance by reducing boxing/unboxing. This unifies and streamlines property accessor infrastructure across all serializers. --- .../AcExpressionNodeSerializationTests.cs | 2 +- ...erializer.BinaryDeserializeTypeMetadata.cs | 2 +- .../Binaries/AcBinaryDeserializer.Populate.cs | 39 +-- .../Binaries/AcBinaryDeserializer.cs | 5 +- ...rySerializer.BinarySerializationContext.cs | 2 +- .../Binaries/AcBinarySerializer.cs | 6 +- .../Binaries/BinaryPropertyAccessorBase.cs | 125 +-------- .../Binaries/BinaryPropertySetterBase.cs | 103 +------- .../Expressions/AcExpressionRebuilder.cs | 2 +- .../Jsons/AcJsonDeserializer.JsonElement.cs | 6 +- .../Jsons/AcJsonDeserializer.Utf8Reader.cs | 6 +- .../Serializers/Jsons/AcJsonSerializer.cs | 4 +- .../Serializers/PropertyAccessorBase.cs | 188 ++++++++++++- AyCode.Core/Serializers/PropertySetterBase.cs | 247 +++++++++++++++++- .../Toons/AcToonSerializer.DataSection.cs | 2 +- .../Serializers/Toons/AcToonSerializer.cs | 2 +- 16 files changed, 457 insertions(+), 284 deletions(-) diff --git a/AyCode.Core.Tests/Serialization/AcExpressionNodeSerializationTests.cs b/AyCode.Core.Tests/Serialization/AcExpressionNodeSerializationTests.cs index 794269e..69eaa3e 100644 --- a/AyCode.Core.Tests/Serialization/AcExpressionNodeSerializationTests.cs +++ b/AyCode.Core.Tests/Serialization/AcExpressionNodeSerializationTests.cs @@ -236,7 +236,7 @@ public class AcExpressionNodeSerializationTests #endregion - #region SetValue/GetValue Tests + #region SetValue/GetDynamicValue Tests [TestMethod] [DataRow(42, ConstantValueType.Int32)] diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializeTypeMetadata.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializeTypeMetadata.cs index 746c28c..d7ca6dc 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializeTypeMetadata.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializeTypeMetadata.cs @@ -151,7 +151,7 @@ public static partial class AcBinaryDeserializer public new bool IsIIdCollection => _isManualConstruction ? _manualIsIIdCollection : base.IsIIdCollection; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public new object? GetValue(object target) => _isManualConstruction ? _manualGetter!(target) : base.GetValue(target); + public new object? GetValue(object target) => _isManualConstruction ? _manualGetter!(target) : base.GetDynamicValue(target); public override void SetValue(object target, object? value) { diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.Populate.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.Populate.cs index f76f7f3..42bca2b 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.Populate.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.Populate.cs @@ -515,43 +515,7 @@ public static partial class AcBinaryDeserializer /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void SetPropertyToDefault(object target, BinaryPropertySetterInfo propInfo) - { - switch (propInfo.SetterType) - { - case PropertyAccessorType.Int32: - propInfo.SetInt32(target, 0); - return; - case PropertyAccessorType.Int64: - propInfo.SetInt64(target, 0L); - return; - case PropertyAccessorType.Boolean: - propInfo.SetBoolean(target, false); - return; - case PropertyAccessorType.Double: - propInfo.SetDouble(target, 0.0); - return; - case PropertyAccessorType.Single: - propInfo.SetSingle(target, 0f); - return; - case PropertyAccessorType.Decimal: - propInfo.SetDecimal(target, 0m); - return; - case PropertyAccessorType.DateTime: - propInfo.SetDateTime(target, default); - return; - case PropertyAccessorType.Guid: - propInfo.SetGuid(target, default); - return; - case PropertyAccessorType.Enum: - propInfo.SetEnumAsInt32(target, 0); - return; - case PropertyAccessorType.Object: - default: - // Reference types and nullable value types: set to null - propInfo.SetValue(target, null); - return; - } - } + => propInfo.SetToDefault(target); /// /// Determines if a type is a complex type (not primitive, string, or simple value type). @@ -573,3 +537,4 @@ public static partial class AcBinaryDeserializer #endregion } + diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs index 7d3d3fd..b3cf9e1 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs @@ -510,11 +510,11 @@ public static partial class AcBinaryDeserializer private static bool TryReadAndSetTypedValue(ref BinaryDeserializationContext context, object target, BinaryPropertySetterInfo propInfo, byte peekCode) { // Only handle if we have a typed setter - if (propInfo.SetterType == PropertyAccessorType.Object) + if (propInfo.AccessorType == PropertyAccessorType.Object) return false; // Handle based on property setter type and incoming data type - switch (propInfo.SetterType) + switch (propInfo.AccessorType) { case PropertyAccessorType.Int32: if (BinaryTypeCode.IsTinyInt(peekCode)) @@ -1474,3 +1474,4 @@ public static partial class AcBinaryDeserializer } // Implementation moved to AcBinaryDeserializer.TypeConversionInfo.cs + diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs index 92a9faf..01d1291 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs @@ -430,7 +430,7 @@ public static partial class AcBinarySerializer property.DeclaringType, property.Name, property.PropertyType, - property.ObjectGetter); + property.DynamicGetter); return PropertyFilter(context); } diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs index 8f68b0d..d299588 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs @@ -747,7 +747,7 @@ public static partial class AcBinarySerializer return false; default: // Object type - use regular getter - var value = prop.GetValue(obj); + var value = prop.GetDynamicValue(obj); if (value == null) return true; if (prop.PropertyTypeCode == TypeCode.String) return string.IsNullOrEmpty((string)value); return false; @@ -818,7 +818,7 @@ public static partial class AcBinarySerializer return; default: // Fallback to object getter for reference types - var value = prop.GetValue(obj); + var value = prop.GetDynamicValue(obj); WriteValue(value, prop.PropertyType, context, depth); return; } @@ -975,7 +975,7 @@ public static partial class AcBinarySerializer default: { // Object type - use regular getter - var value = prop.GetValue(obj); + var value = prop.GetDynamicValue(obj); // SKIP marker only for null (reference types) // Empty string, empty collections, etc. are valid values and must be written! diff --git a/AyCode.Core/Serializers/Binaries/BinaryPropertyAccessorBase.cs b/AyCode.Core/Serializers/Binaries/BinaryPropertyAccessorBase.cs index 2f60f88..e0d1991 100644 --- a/AyCode.Core/Serializers/Binaries/BinaryPropertyAccessorBase.cs +++ b/AyCode.Core/Serializers/Binaries/BinaryPropertyAccessorBase.cs @@ -5,8 +5,9 @@ using static AyCode.Core.Helpers.JsonUtilities; namespace AyCode.Core.Serializers.Binaries; /// -/// Binary-specific property accessor base class. -/// Adds typed getters to avoid boxing during serialization. +/// Binary-specific property accessor. +/// Inherits typed getters from PropertyAccessorBase. +/// Adds Binary-specific properties: PropertyIndex, CachedPropertyNameIndex. /// public abstract class BinaryPropertyAccessorBase : PropertyAccessorBase { @@ -23,130 +24,14 @@ public abstract class BinaryPropertyAccessorBase : PropertyAccessorBase /// public int PropertyIndex { get; internal set; } = -1; - /// - /// The accessor type for fast typed getter dispatch. - /// - public PropertyAccessorType AccessorType { get; } - - /// - /// Typed getter delegate (type depends on AccessorType). - /// - protected readonly Delegate? _typedGetter; - /// /// Object getter for property filter context. /// - public Func ObjectGetter => _getter; + public Func DynamicGetter => _dynamicGetter; protected BinaryPropertyAccessorBase(PropertyInfo prop, Type declaringType) : base(prop, declaringType) { - (_typedGetter, AccessorType) = CreateTypedGetterForAccessor(declaringType, prop); + // All typed getters are initialized in PropertyAccessorBase } - - private static (Delegate?, PropertyAccessorType) CreateTypedGetterForAccessor(Type declaringType, PropertyInfo prop) - { - var propType = prop.PropertyType; - var underlying = Nullable.GetUnderlyingType(propType); - if (underlying != null) - { - return (null, PropertyAccessorType.Object); - } - - if (propType.IsEnum) - { - return (AcSerializerCommon.CreateEnumGetter(declaringType, prop), PropertyAccessorType.Enum); - } - - if (ReferenceEquals(propType, GuidType)) - { - return (AcSerializerCommon.CreateTypedGetter(declaringType, prop), PropertyAccessorType.Guid); - } - - var typeCode = Type.GetTypeCode(propType); - return typeCode switch - { - TypeCode.Int32 => (AcSerializerCommon.CreateTypedGetter(declaringType, prop), PropertyAccessorType.Int32), - TypeCode.Int64 => (AcSerializerCommon.CreateTypedGetter(declaringType, prop), PropertyAccessorType.Int64), - TypeCode.Boolean => (AcSerializerCommon.CreateTypedGetter(declaringType, prop), PropertyAccessorType.Boolean), - TypeCode.Double => (AcSerializerCommon.CreateTypedGetter(declaringType, prop), PropertyAccessorType.Double), - TypeCode.Single => (AcSerializerCommon.CreateTypedGetter(declaringType, prop), PropertyAccessorType.Single), - TypeCode.Decimal => (AcSerializerCommon.CreateTypedGetter(declaringType, prop), PropertyAccessorType.Decimal), - TypeCode.DateTime => (AcSerializerCommon.CreateTypedGetter(declaringType, prop), PropertyAccessorType.DateTime), - TypeCode.Byte => (AcSerializerCommon.CreateTypedGetter(declaringType, prop), PropertyAccessorType.Byte), - TypeCode.Int16 => (AcSerializerCommon.CreateTypedGetter(declaringType, prop), PropertyAccessorType.Int16), - TypeCode.UInt16 => (AcSerializerCommon.CreateTypedGetter(declaringType, prop), PropertyAccessorType.UInt16), - TypeCode.UInt32 => (AcSerializerCommon.CreateTypedGetter(declaringType, prop), PropertyAccessorType.UInt32), - TypeCode.UInt64 => (AcSerializerCommon.CreateTypedGetter(declaringType, prop), PropertyAccessorType.UInt64), - _ => (null, PropertyAccessorType.Object) - }; - } - - #region Typed Getters - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int GetInt32(object obj) => ((Func)_typedGetter!)(obj); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public long GetInt64(object obj) => ((Func)_typedGetter!)(obj); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool GetBoolean(object obj) => ((Func)_typedGetter!)(obj); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public double GetDouble(object obj) => ((Func)_typedGetter!)(obj); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public float GetSingle(object obj) => ((Func)_typedGetter!)(obj); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public decimal GetDecimal(object obj) => ((Func)_typedGetter!)(obj); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public DateTime GetDateTime(object obj) => ((Func)_typedGetter!)(obj); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte GetByte(object obj) => ((Func)_typedGetter!)(obj); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public short GetInt16(object obj) => ((Func)_typedGetter!)(obj); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ushort GetUInt16(object obj) => ((Func)_typedGetter!)(obj); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public uint GetUInt32(object obj) => ((Func)_typedGetter!)(obj); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ulong GetUInt64(object obj) => ((Func)_typedGetter!)(obj); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Guid GetGuid(object obj) => ((Func)_typedGetter!)(obj); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int GetEnumAsInt32(object obj) => ((Func)_typedGetter!)(obj); - - #endregion -} - -/// -/// Enum for typed property accessor dispatch. -/// -public enum PropertyAccessorType : byte -{ - Object = 0, - Int32, - Int64, - Boolean, - Double, - Single, - Decimal, - DateTime, - Byte, - Int16, - UInt16, - UInt32, - UInt64, - Guid, - Enum } diff --git a/AyCode.Core/Serializers/Binaries/BinaryPropertySetterBase.cs b/AyCode.Core/Serializers/Binaries/BinaryPropertySetterBase.cs index 6baa1f3..ca04990 100644 --- a/AyCode.Core/Serializers/Binaries/BinaryPropertySetterBase.cs +++ b/AyCode.Core/Serializers/Binaries/BinaryPropertySetterBase.cs @@ -7,124 +7,29 @@ namespace AyCode.Core.Serializers.Binaries; /// /// Binary-specific property setter base class. -/// Extends PropertySetterBase with binary-specific functionality and typed setters. +/// Extends PropertySetterBase with binary-specific functionality. +/// Typed setters are inherited from PropertySetterBase. /// public abstract class BinaryPropertySetterBase : PropertySetterBase { /// /// Whether this property is a complex type (not primitive, string, enum, or common value types). + /// Note: Shadows PropertyAccessorBase.IsComplexType with Binary-specific check. /// - public bool IsComplexType { get; } + public new bool IsComplexType { get; } /// /// Whether this property is a collection type. /// public bool IsCollection { get; } - /// - /// The setter type for fast typed setter dispatch. - /// - public PropertyAccessorType SetterType { get; } - - /// - /// Typed setter delegate (type depends on SetterType). - /// - protected readonly Delegate? _typedSetter; - protected BinaryPropertySetterBase(PropertyInfo prop, Type declaringType) : base(prop, declaringType) { IsCollection = IsCollectionTypeCheck(PropertyType); IsComplexType = IsComplex(PropertyType); - (_typedSetter, SetterType) = CreateTypedSetterForAccessor(declaringType, prop); } - private static (Delegate?, PropertyAccessorType) CreateTypedSetterForAccessor(Type declaringType, PropertyInfo prop) - { - var propType = prop.PropertyType; - var underlying = Nullable.GetUnderlyingType(propType); - if (underlying != null) - { - // Nullable types use Object path - return (null, PropertyAccessorType.Object); - } - - if (propType.IsEnum) - { - return (AcSerializerCommon.CreateEnumSetter(declaringType, prop), PropertyAccessorType.Enum); - } - - if (ReferenceEquals(propType, GuidType)) - { - return (AcSerializerCommon.CreateTypedSetter(declaringType, prop), PropertyAccessorType.Guid); - } - - var typeCode = Type.GetTypeCode(propType); - return typeCode switch - { - TypeCode.Int32 => (AcSerializerCommon.CreateTypedSetter(declaringType, prop), PropertyAccessorType.Int32), - TypeCode.Int64 => (AcSerializerCommon.CreateTypedSetter(declaringType, prop), PropertyAccessorType.Int64), - TypeCode.Boolean => (AcSerializerCommon.CreateTypedSetter(declaringType, prop), PropertyAccessorType.Boolean), - TypeCode.Double => (AcSerializerCommon.CreateTypedSetter(declaringType, prop), PropertyAccessorType.Double), - TypeCode.Single => (AcSerializerCommon.CreateTypedSetter(declaringType, prop), PropertyAccessorType.Single), - TypeCode.Decimal => (AcSerializerCommon.CreateTypedSetter(declaringType, prop), PropertyAccessorType.Decimal), - TypeCode.DateTime => (AcSerializerCommon.CreateTypedSetter(declaringType, prop), PropertyAccessorType.DateTime), - TypeCode.Byte => (AcSerializerCommon.CreateTypedSetter(declaringType, prop), PropertyAccessorType.Byte), - TypeCode.Int16 => (AcSerializerCommon.CreateTypedSetter(declaringType, prop), PropertyAccessorType.Int16), - TypeCode.UInt16 => (AcSerializerCommon.CreateTypedSetter(declaringType, prop), PropertyAccessorType.UInt16), - TypeCode.UInt32 => (AcSerializerCommon.CreateTypedSetter(declaringType, prop), PropertyAccessorType.UInt32), - TypeCode.UInt64 => (AcSerializerCommon.CreateTypedSetter(declaringType, prop), PropertyAccessorType.UInt64), - TypeCode.String => (null, PropertyAccessorType.Object), // String doesn't benefit from typed setter - _ => (null, PropertyAccessorType.Object) - }; - } - - #region Typed Setters - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetInt32(object obj, int value) => ((Action)_typedSetter!)(obj, value); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetInt64(object obj, long value) => ((Action)_typedSetter!)(obj, value); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetBoolean(object obj, bool value) => ((Action)_typedSetter!)(obj, value); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetDouble(object obj, double value) => ((Action)_typedSetter!)(obj, value); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetSingle(object obj, float value) => ((Action)_typedSetter!)(obj, value); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetDecimal(object obj, decimal value) => ((Action)_typedSetter!)(obj, value); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetDateTime(object obj, DateTime value) => ((Action)_typedSetter!)(obj, value); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetByte(object obj, byte value) => ((Action)_typedSetter!)(obj, value); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetInt16(object obj, short value) => ((Action)_typedSetter!)(obj, value); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetUInt16(object obj, ushort value) => ((Action)_typedSetter!)(obj, value); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetUInt32(object obj, uint value) => ((Action)_typedSetter!)(obj, value); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetUInt64(object obj, ulong value) => ((Action)_typedSetter!)(obj, value); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetGuid(object obj, Guid value) => ((Action)_typedSetter!)(obj, value); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetEnumAsInt32(object obj, int value) => ((Action)_typedSetter!)(obj, value); - - #endregion - public override void SetValue(object target, object? value) { try diff --git a/AyCode.Core/Serializers/Expressions/AcExpressionRebuilder.cs b/AyCode.Core/Serializers/Expressions/AcExpressionRebuilder.cs index 290e80d..9f92b51 100644 --- a/AyCode.Core/Serializers/Expressions/AcExpressionRebuilder.cs +++ b/AyCode.Core/Serializers/Expressions/AcExpressionRebuilder.cs @@ -119,7 +119,7 @@ public class AcExpressionRebuilder { var type = ResolveType(node.TypeName ?? "System.Object"); - // Use the type-safe GetValue method + // Use the type-safe GetDynamicValue method var value = node.GetValue(); if (value == null) diff --git a/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.JsonElement.cs b/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.JsonElement.cs index 706fe3c..050aa2e 100644 --- a/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.JsonElement.cs +++ b/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.JsonElement.cs @@ -116,7 +116,7 @@ public static partial class AcJsonDeserializer for (var i = 0; i < props.Length; i++) { var prop = props[i]; - var value = prop.GetValue(source); + var value = prop.GetDynamicValue(source); if (value != null) prop.SetValue(target, value); } @@ -176,7 +176,7 @@ public static partial class AcJsonDeserializer // Handle IId collection merge if (propInfo.IsIIdCollection && propValueKind == JsonValueKind.Array) { - var existingCollection = propInfo.GetValue(target); + var existingCollection = propInfo.GetDynamicValue(target); if (existingCollection != null) { MergeIIdCollection(propValue, existingCollection, propInfo, context, depth); @@ -201,7 +201,7 @@ public static partial class AcJsonDeserializer // Merge into existing object if (!propInfo.PropertyType.IsPrimitive && !ReferenceEquals(propInfo.PropertyType, StringType)) { - var existingObj = propInfo.GetValue(target); + var existingObj = propInfo.GetDynamicValue(target); if (existingObj != null) { var nestedMetadata = GetTypeMetadata(propInfo.PropertyType); diff --git a/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.Utf8Reader.cs b/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.Utf8Reader.cs index d2a4657..dd4b407 100644 --- a/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.Utf8Reader.cs +++ b/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.Utf8Reader.cs @@ -489,7 +489,7 @@ public static partial class AcJsonDeserializer // Handle IId collection merge if (propInfo.IsIIdCollection && tokenType == JsonTokenType.StartArray) { - var existingCollection = propInfo.GetValue(target); + var existingCollection = propInfo.GetDynamicValue(target); if (existingCollection != null) { MergeIIdCollectionFromReader(ref reader, existingCollection, propInfo, maxDepth, depth); @@ -500,7 +500,7 @@ public static partial class AcJsonDeserializer // Handle nested objects - merge into existing if (tokenType == JsonTokenType.StartObject && !propInfo.PropertyType.IsPrimitive && !ReferenceEquals(propInfo.PropertyType, StringType)) { - var existingObj = propInfo.GetValue(target); + var existingObj = propInfo.GetDynamicValue(target); if (existingObj != null) { var nestedMetadata = GetTypeMetadata(propInfo.PropertyType); @@ -611,7 +611,7 @@ public static partial class AcJsonDeserializer { foreach (var prop in metadata.PropertySettersFrozen.Values) { - var value = prop.GetValue(source); + var value = prop.GetDynamicValue(source); if (value != null) prop.SetValue(target, value); } diff --git a/AyCode.Core/Serializers/Jsons/AcJsonSerializer.cs b/AyCode.Core/Serializers/Jsons/AcJsonSerializer.cs index 02e1b4b..a16c277 100644 --- a/AyCode.Core/Serializers/Jsons/AcJsonSerializer.cs +++ b/AyCode.Core/Serializers/Jsons/AcJsonSerializer.cs @@ -149,7 +149,7 @@ public static partial class AcJsonSerializer var propCount = props.Length; for (var i = 0; i < propCount; i++) { - var propValue = props[i].GetValue(value); + var propValue = props[i].GetDynamicValue(value); if (propValue != null) ScanReferences(propValue, context, depth + 1); } } @@ -205,7 +205,7 @@ public static partial class AcJsonSerializer for (var i = 0; i < propCount; i++) { var prop = props[i]; - var propValue = prop.GetValue(value); + var propValue = prop.GetDynamicValue(value); if (propValue == null) continue; if (IsDefaultValueFast(propValue, prop.PropertyTypeCode, prop.PropertyType)) continue; diff --git a/AyCode.Core/Serializers/PropertyAccessorBase.cs b/AyCode.Core/Serializers/PropertyAccessorBase.cs index 9519104..59cea81 100644 --- a/AyCode.Core/Serializers/PropertyAccessorBase.cs +++ b/AyCode.Core/Serializers/PropertyAccessorBase.cs @@ -5,9 +5,32 @@ using static AyCode.Core.Helpers.JsonUtilities; namespace AyCode.Core.Serializers; +/// +/// Enum for typed property accessor dispatch. +/// +public enum PropertyAccessorType : byte +{ + Object = 0, + Int32, + Int64, + Boolean, + Double, + Single, + Decimal, + DateTime, + Byte, + Int16, + UInt16, + UInt32, + UInt64, + Guid, + Enum +} + /// /// Base class for property accessors used by all serializers. -/// Contains common property metadata and getter functionality. +/// Contains common property metadata, getter functionality, and typed delegate fields. +/// Typed getters eliminate runtime cast overhead for value type properties. /// public abstract class PropertyAccessorBase { @@ -53,9 +76,33 @@ public abstract class PropertyAccessorBase public bool IsComplexType { get; } /// - /// Compiled getter delegate for reading property values. + /// The accessor type for fast typed getter dispatch. /// - protected readonly Func _getter; + public PropertyAccessorType AccessorType { get; } + + /// + /// Compiled getter delegate for reading property values (boxed). + /// + protected readonly Func _dynamicGetter; + + #region Strongly-typed getter delegate fields (eliminates runtime cast) + + // Only ONE of these is set based on AccessorType + private readonly Func? _int32Getter; + private readonly Func? _int64Getter; + private readonly Func? _boolGetter; + private readonly Func? _doubleGetter; + private readonly Func? _floatGetter; + private readonly Func? _decimalGetter; + private readonly Func? _dateTimeGetter; + private readonly Func? _byteGetter; + private readonly Func? _int16Getter; + private readonly Func? _uint16Getter; + private readonly Func? _uint32Getter; + private readonly Func? _uint64Getter; + private readonly Func? _guidGetter; + + #endregion protected PropertyAccessorBase(PropertyInfo prop, Type declaringType) { @@ -72,12 +119,141 @@ public abstract class PropertyAccessorBase // Pre-compute: is this a complex type that needs recursive handling? IsComplexType = !IsPrimitiveOrStringFast(PropertyType); - _getter = AcSerializerCommon.CreateCompiledGetter(declaringType, prop); + _dynamicGetter = AcSerializerCommon.CreateCompiledGetter(declaringType, prop); + + // Initialize typed getter + AccessorType = DetermineAccessorType(PropertyType); + 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) + { + switch (AccessorType) + { + case PropertyAccessorType.Int32: + Unsafe.AsRef(in _int32Getter) = AcSerializerCommon.CreateTypedGetter(declaringType, prop); + break; + case PropertyAccessorType.Int64: + Unsafe.AsRef(in _int64Getter) = AcSerializerCommon.CreateTypedGetter(declaringType, prop); + break; + case PropertyAccessorType.Boolean: + Unsafe.AsRef(in _boolGetter) = AcSerializerCommon.CreateTypedGetter(declaringType, prop); + break; + case PropertyAccessorType.Double: + Unsafe.AsRef(in _doubleGetter) = AcSerializerCommon.CreateTypedGetter(declaringType, prop); + break; + case PropertyAccessorType.Single: + Unsafe.AsRef(in _floatGetter) = AcSerializerCommon.CreateTypedGetter(declaringType, prop); + break; + case PropertyAccessorType.Decimal: + Unsafe.AsRef(in _decimalGetter) = AcSerializerCommon.CreateTypedGetter(declaringType, prop); + break; + case PropertyAccessorType.DateTime: + Unsafe.AsRef(in _dateTimeGetter) = AcSerializerCommon.CreateTypedGetter(declaringType, prop); + break; + case PropertyAccessorType.Byte: + Unsafe.AsRef(in _byteGetter) = AcSerializerCommon.CreateTypedGetter(declaringType, prop); + break; + case PropertyAccessorType.Int16: + Unsafe.AsRef(in _int16Getter) = AcSerializerCommon.CreateTypedGetter(declaringType, prop); + break; + case PropertyAccessorType.UInt16: + Unsafe.AsRef(in _uint16Getter) = AcSerializerCommon.CreateTypedGetter(declaringType, prop); + break; + case PropertyAccessorType.UInt32: + Unsafe.AsRef(in _uint32Getter) = AcSerializerCommon.CreateTypedGetter(declaringType, prop); + break; + case PropertyAccessorType.UInt64: + Unsafe.AsRef(in _uint64Getter) = AcSerializerCommon.CreateTypedGetter(declaringType, prop); + break; + case PropertyAccessorType.Guid: + Unsafe.AsRef(in _guidGetter) = AcSerializerCommon.CreateTypedGetter(declaringType, prop); + break; + case PropertyAccessorType.Enum: + Unsafe.AsRef(in _int32Getter) = AcSerializerCommon.CreateEnumGetter(declaringType, prop); + break; + } + } + + #region Typed Getters - Direct invocation, no cast! + /// - /// Gets the property value from the target object. + /// Gets the property value from the target object (boxed). /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public object? GetValue(object obj) => _getter(obj); + public object? GetDynamicValue(object obj) => _dynamicGetter(obj); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetInt32(object obj) => _int32Getter!(obj); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long GetInt64(object obj) => _int64Getter!(obj); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool GetBoolean(object obj) => _boolGetter!(obj); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public double GetDouble(object obj) => _doubleGetter!(obj); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float GetSingle(object obj) => _floatGetter!(obj); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public decimal GetDecimal(object obj) => _decimalGetter!(obj); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public DateTime GetDateTime(object obj) => _dateTimeGetter!(obj); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte GetByte(object obj) => _byteGetter!(obj); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public short GetInt16(object obj) => _int16Getter!(obj); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort GetUInt16(object obj) => _uint16Getter!(obj); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint GetUInt32(object obj) => _uint32Getter!(obj); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ulong GetUInt64(object obj) => _uint64Getter!(obj); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Guid GetGuid(object obj) => _guidGetter!(obj); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetEnumAsInt32(object obj) => _int32Getter!(obj); + + #endregion } diff --git a/AyCode.Core/Serializers/PropertySetterBase.cs b/AyCode.Core/Serializers/PropertySetterBase.cs index 9284846..a16c5d1 100644 --- a/AyCode.Core/Serializers/PropertySetterBase.cs +++ b/AyCode.Core/Serializers/PropertySetterBase.cs @@ -7,12 +7,12 @@ namespace AyCode.Core.Serializers; /// /// Base class for property accessors that also support setting values. -/// Used by deserializers. Extends PropertyAccessorBase with setter and IId collection support. +/// Used by deserializers. Extends PropertyAccessorBase with typed setters and IId collection support. /// public abstract class PropertySetterBase : PropertyAccessorBase { /// - /// Compiled setter delegate for writing property values. + /// Compiled setter delegate for writing property values (boxed). /// protected readonly Action _setter; @@ -36,11 +36,33 @@ public abstract class PropertySetterBase : PropertyAccessorBase /// public Func? ElementIdGetter { get; } + #region Strongly-typed setter delegate fields (eliminates runtime cast for deserialize) + + // Only ONE of these is set based on AccessorType + private readonly Action? _int32Setter; + private readonly Action? _int64Setter; + private readonly Action? _boolSetter; + private readonly Action? _doubleSetter; + private readonly Action? _floatSetter; + private readonly Action? _decimalSetter; + private readonly Action? _dateTimeSetter; + private readonly Action? _byteSetter; + private readonly Action? _int16Setter; + private readonly Action? _uint16Setter; + private readonly Action? _uint32Setter; + private readonly Action? _uint64Setter; + private readonly Action? _guidSetter; + + #endregion + protected PropertySetterBase(PropertyInfo prop, Type declaringType) : base(prop, declaringType) { _setter = AcSerializerCommon.CreateCompiledSetter(declaringType, prop); + // Initialize typed setter + InitializeTypedSetter(declaringType, prop); + // Determine collection element type ElementType = GetCollectionElementType(PropertyType); @@ -63,9 +85,228 @@ public abstract class PropertySetterBase : PropertyAccessorBase } } + private void InitializeTypedSetter(Type declaringType, PropertyInfo prop) + { + switch (AccessorType) + { + case PropertyAccessorType.Int32: + Unsafe.AsRef(in _int32Setter) = AcSerializerCommon.CreateTypedSetter(declaringType, prop); + break; + case PropertyAccessorType.Int64: + Unsafe.AsRef(in _int64Setter) = AcSerializerCommon.CreateTypedSetter(declaringType, prop); + break; + case PropertyAccessorType.Boolean: + Unsafe.AsRef(in _boolSetter) = AcSerializerCommon.CreateTypedSetter(declaringType, prop); + break; + case PropertyAccessorType.Double: + Unsafe.AsRef(in _doubleSetter) = AcSerializerCommon.CreateTypedSetter(declaringType, prop); + break; + case PropertyAccessorType.Single: + Unsafe.AsRef(in _floatSetter) = AcSerializerCommon.CreateTypedSetter(declaringType, prop); + break; + case PropertyAccessorType.Decimal: + Unsafe.AsRef(in _decimalSetter) = AcSerializerCommon.CreateTypedSetter(declaringType, prop); + break; + case PropertyAccessorType.DateTime: + Unsafe.AsRef(in _dateTimeSetter) = AcSerializerCommon.CreateTypedSetter(declaringType, prop); + break; + case PropertyAccessorType.Byte: + Unsafe.AsRef(in _byteSetter) = AcSerializerCommon.CreateTypedSetter(declaringType, prop); + break; + case PropertyAccessorType.Int16: + Unsafe.AsRef(in _int16Setter) = AcSerializerCommon.CreateTypedSetter(declaringType, prop); + break; + case PropertyAccessorType.UInt16: + Unsafe.AsRef(in _uint16Setter) = AcSerializerCommon.CreateTypedSetter(declaringType, prop); + break; + case PropertyAccessorType.UInt32: + Unsafe.AsRef(in _uint32Setter) = AcSerializerCommon.CreateTypedSetter(declaringType, prop); + break; + case PropertyAccessorType.UInt64: + Unsafe.AsRef(in _uint64Setter) = AcSerializerCommon.CreateTypedSetter(declaringType, prop); + break; + case PropertyAccessorType.Guid: + Unsafe.AsRef(in _guidSetter) = AcSerializerCommon.CreateTypedSetter(declaringType, prop); + break; + case PropertyAccessorType.Enum: + Unsafe.AsRef(in _int32Setter) = AcSerializerCommon.CreateEnumSetter(declaringType, prop); + break; + } + } + + #region Typed Setters - Direct invocation, no cast! + /// - /// Sets the property value on the target object. + /// Sets the property value on the target object (boxed). /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public virtual void SetValue(object target, object? value) => _setter(target, value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetInt32(object obj, int value) => _int32Setter!(obj, value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetInt64(object obj, long value) => _int64Setter!(obj, value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetBoolean(object obj, bool value) => _boolSetter!(obj, value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetDouble(object obj, double value) => _doubleSetter!(obj, value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetSingle(object obj, float value) => _floatSetter!(obj, value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetDecimal(object obj, decimal value) => _decimalSetter!(obj, value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetDateTime(object obj, DateTime value) => _dateTimeSetter!(obj, value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetByte(object obj, byte value) => _byteSetter!(obj, value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetInt16(object obj, short value) => _int16Setter!(obj, value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetUInt16(object obj, ushort value) => _uint16Setter!(obj, value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetUInt32(object obj, uint value) => _uint32Setter!(obj, value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetUInt64(object obj, ulong value) => _uint64Setter!(obj, value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetGuid(object obj, Guid value) => _guidSetter!(obj, value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetEnumAsInt32(object obj, int value) => _int32Setter!(obj, value); + + #endregion + + #region Dispatch helpers - centralized switch for already-boxed values + + /// + /// Sets property from already-boxed value using typed setter for unboxing optimization. + /// Use when value is already boxed (e.g., from ReadValue). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetValueTyped(object target, object? value) + { + if (value == null) + { + SetValue(target, null); + return; + } + + switch (AccessorType) + { + case PropertyAccessorType.Int32: + SetInt32(target, (int)value); + return; + case PropertyAccessorType.Int64: + SetInt64(target, (long)value); + return; + case PropertyAccessorType.Boolean: + SetBoolean(target, (bool)value); + return; + case PropertyAccessorType.Double: + SetDouble(target, (double)value); + return; + case PropertyAccessorType.Single: + SetSingle(target, (float)value); + return; + case PropertyAccessorType.Decimal: + SetDecimal(target, (decimal)value); + return; + case PropertyAccessorType.DateTime: + SetDateTime(target, (DateTime)value); + return; + case PropertyAccessorType.Byte: + SetByte(target, (byte)value); + return; + case PropertyAccessorType.Int16: + SetInt16(target, (short)value); + return; + case PropertyAccessorType.UInt16: + SetUInt16(target, (ushort)value); + return; + case PropertyAccessorType.UInt32: + SetUInt32(target, (uint)value); + return; + case PropertyAccessorType.UInt64: + SetUInt64(target, (ulong)value); + return; + case PropertyAccessorType.Guid: + SetGuid(target, (Guid)value); + return; + case PropertyAccessorType.Enum: + SetEnumAsInt32(target, (int)value); + return; + default: + SetValue(target, value); + return; + } + } + + /// + /// Sets property to its type's default value using typed setter. + /// Avoids boxing for value types. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetToDefault(object target) + { + switch (AccessorType) + { + case PropertyAccessorType.Int32: + SetInt32(target, 0); + return; + case PropertyAccessorType.Int64: + SetInt64(target, 0L); + return; + case PropertyAccessorType.Boolean: + SetBoolean(target, false); + return; + case PropertyAccessorType.Double: + SetDouble(target, 0.0); + return; + case PropertyAccessorType.Single: + SetSingle(target, 0f); + return; + case PropertyAccessorType.Decimal: + SetDecimal(target, 0m); + return; + case PropertyAccessorType.DateTime: + SetDateTime(target, default); + return; + case PropertyAccessorType.Byte: + SetByte(target, 0); + return; + case PropertyAccessorType.Int16: + SetInt16(target, 0); + return; + case PropertyAccessorType.UInt16: + SetUInt16(target, 0); + return; + case PropertyAccessorType.UInt32: + SetUInt32(target, 0); + return; + case PropertyAccessorType.UInt64: + SetUInt64(target, 0); + return; + case PropertyAccessorType.Guid: + SetGuid(target, Guid.Empty); + return; + case PropertyAccessorType.Enum: + SetEnumAsInt32(target, 0); + return; + default: + SetValue(target, null); + return; + } + } + + #endregion } diff --git a/AyCode.Core/Serializers/Toons/AcToonSerializer.DataSection.cs b/AyCode.Core/Serializers/Toons/AcToonSerializer.DataSection.cs index 913adc0..3a5a96d 100644 --- a/AyCode.Core/Serializers/Toons/AcToonSerializer.DataSection.cs +++ b/AyCode.Core/Serializers/Toons/AcToonSerializer.DataSection.cs @@ -247,7 +247,7 @@ public static partial class AcToonSerializer // Write properties foreach (var prop in metadata.Properties) { - var propValue = prop.GetValue(value); + var propValue = prop.GetDynamicValue(value); // Skip null/default values if option is set if (context.Options.OmitDefaultValues && prop.IsDefaultValue(propValue)) diff --git a/AyCode.Core/Serializers/Toons/AcToonSerializer.cs b/AyCode.Core/Serializers/Toons/AcToonSerializer.cs index 406502d..928e613 100644 --- a/AyCode.Core/Serializers/Toons/AcToonSerializer.cs +++ b/AyCode.Core/Serializers/Toons/AcToonSerializer.cs @@ -313,7 +313,7 @@ public static partial class AcToonSerializer var metadata = GetTypeMetadata(type); foreach (var prop in metadata.Properties) { - var propValue = prop.GetValue(value); + var propValue = prop.GetDynamicValue(value); if (propValue != null) ScanReferences(propValue, context, depth + 1); } }