AyCode.Core/AyCode.Core/Serializers/PropertySetterBase.cs

313 lines
12 KiB
C#

using System.Collections;
using System.Reflection;
using System.Runtime.CompilerServices;
using static AyCode.Core.Helpers.JsonUtilities;
namespace AyCode.Core.Serializers;
/// <summary>
/// Base class for property accessors that also support setting values.
/// Used by deserializers. Extends PropertyAccessorBase with typed setters and IId collection support.
/// </summary>
public abstract class PropertySetterBase : PropertyAccessorBase
{
/// <summary>
/// Compiled setter delegate for writing property values (boxed).
/// </summary>
protected readonly Action<object, object?> _setter;
/// <summary>
/// Whether this property is a collection of IId elements.
/// </summary>
public bool IsIIdCollection { get; }
/// <summary>
/// Element type if this property is a collection, null otherwise.
/// </summary>
public Type? ElementType { get; }
/// <summary>
/// The Id type of collection elements (if IsIIdCollection is true).
/// </summary>
public Type? ElementIdType { get; }
/// <summary>
/// Compiled getter for the Id property of collection elements.
/// </summary>
public Func<object, object?>? 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<object, int>? _int32Setter;
private readonly Action<object, long>? _int64Setter;
private readonly Action<object, bool>? _boolSetter;
private readonly Action<object, double>? _doubleSetter;
private readonly Action<object, float>? _floatSetter;
private readonly Action<object, decimal>? _decimalSetter;
private readonly Action<object, DateTime>? _dateTimeSetter;
private readonly Action<object, byte>? _byteSetter;
private readonly Action<object, short>? _int16Setter;
private readonly Action<object, ushort>? _uint16Setter;
private readonly Action<object, uint>? _uint32Setter;
private readonly Action<object, ulong>? _uint64Setter;
private readonly Action<object, Guid>? _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);
var isCollection = ElementType != null &&
ElementType != typeof(object) &&
typeof(IEnumerable).IsAssignableFrom(PropertyType) &&
!ReferenceEquals(PropertyType, StringType);
if (isCollection && ElementType != null)
{
var idInfo = GetIdInfo(ElementType);
if (idInfo.IsId)
{
IsIIdCollection = true;
ElementIdType = idInfo.IdType;
var idProp = ElementType.GetProperty("Id");
if (idProp != null)
ElementIdGetter = AcSerializerCommon.CreateCompiledGetter(ElementType, idProp);
}
}
}
private void InitializeTypedSetter(Type declaringType, PropertyInfo prop)
{
switch (AccessorType)
{
case PropertyAccessorType.Int32:
Unsafe.AsRef(in _int32Setter) = AcSerializerCommon.CreateTypedSetter<int>(declaringType, prop);
break;
case PropertyAccessorType.Int64:
Unsafe.AsRef(in _int64Setter) = AcSerializerCommon.CreateTypedSetter<long>(declaringType, prop);
break;
case PropertyAccessorType.Boolean:
Unsafe.AsRef(in _boolSetter) = AcSerializerCommon.CreateTypedSetter<bool>(declaringType, prop);
break;
case PropertyAccessorType.Double:
Unsafe.AsRef(in _doubleSetter) = AcSerializerCommon.CreateTypedSetter<double>(declaringType, prop);
break;
case PropertyAccessorType.Single:
Unsafe.AsRef(in _floatSetter) = AcSerializerCommon.CreateTypedSetter<float>(declaringType, prop);
break;
case PropertyAccessorType.Decimal:
Unsafe.AsRef(in _decimalSetter) = AcSerializerCommon.CreateTypedSetter<decimal>(declaringType, prop);
break;
case PropertyAccessorType.DateTime:
Unsafe.AsRef(in _dateTimeSetter) = AcSerializerCommon.CreateTypedSetter<DateTime>(declaringType, prop);
break;
case PropertyAccessorType.Byte:
Unsafe.AsRef(in _byteSetter) = AcSerializerCommon.CreateTypedSetter<byte>(declaringType, prop);
break;
case PropertyAccessorType.Int16:
Unsafe.AsRef(in _int16Setter) = AcSerializerCommon.CreateTypedSetter<short>(declaringType, prop);
break;
case PropertyAccessorType.UInt16:
Unsafe.AsRef(in _uint16Setter) = AcSerializerCommon.CreateTypedSetter<ushort>(declaringType, prop);
break;
case PropertyAccessorType.UInt32:
Unsafe.AsRef(in _uint32Setter) = AcSerializerCommon.CreateTypedSetter<uint>(declaringType, prop);
break;
case PropertyAccessorType.UInt64:
Unsafe.AsRef(in _uint64Setter) = AcSerializerCommon.CreateTypedSetter<ulong>(declaringType, prop);
break;
case PropertyAccessorType.Guid:
Unsafe.AsRef(in _guidSetter) = AcSerializerCommon.CreateTypedSetter<Guid>(declaringType, prop);
break;
case PropertyAccessorType.Enum:
Unsafe.AsRef(in _int32Setter) = AcSerializerCommon.CreateEnumSetter(declaringType, prop);
break;
}
}
#region Typed Setters - Direct invocation, no cast!
/// <summary>
/// Sets the property value on the target object (boxed).
/// </summary>
[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
/// <summary>
/// Sets property from already-boxed value using typed setter for unboxing optimization.
/// Use when value is already boxed (e.g., from ReadValue).
/// </summary>
[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;
}
}
/// <summary>
/// Sets property to its type's default value using typed setter.
/// Avoids boxing for value types.
/// </summary>
[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
}