AyCode.Core/AyCode.Core/Serializers/Binaries/BinaryPropertyAccessorBase.cs

118 lines
5.1 KiB
C#

using System.Reflection;
using System.Runtime.CompilerServices;
using static AyCode.Core.Helpers.JsonUtilities;
namespace AyCode.Core.Serializers.Binaries;
/// <summary>
/// Binary-specific property accessor.
/// Inherits typed getters from PropertyAccessorBase.
/// Adds Binary-specific properties: PropertyIndex, IsStringInternProperty.
/// </summary>
public abstract class BinaryPropertyAccessorBase : PropertyAccessorBase
{
/// <summary>
/// Deterministic property index based on alphabetical ordering of property names.
/// This is computed once during metadata creation and is consistent across all platforms.
/// Used for fast serialization without dictionary lookup.
/// </summary>
public int PropertyIndex { get; internal set; } = -1;
/// <summary>
/// Index into the PropertyTypeWrappers array on TypeMetadataWrapper.
/// Only set for complex (non-primitive, non-string) properties. -1 for primitives and strings.
/// Used to pre-cache TypeMetadataWrapper per property type, eliminating GetWrapper dictionary lookups.
/// </summary>
public int ComplexPropertyIndex { get; internal set; } = -1;
public bool IsStringCollectionProperty { get; }
/// <summary>
/// Cached [AcStringIntern] attribute value for this property.
/// null = no attribute (follow global StringInterningMode)
/// true = [AcStringIntern(true)] — force intern
/// false = [AcStringIntern(false)] — force skip
/// </summary>
private readonly byte _interningFlags;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool UseStringPropertyInterning(StringInterningMode stringInterningMode)
{
return (_interningFlags & (1 << (int)stringInterningMode)) != 0;
}
/// <summary>
/// Object getter for property filter context.
/// </summary>
public Func<object, object?> DynamicGetter => _dynamicGetter;
/// <summary>
/// Pre-computed expected type code for markerless serialization (UseMetadata=false).
/// When set, the serializer skips writing the type marker byte and the deserializer
/// uses this value instead of reading from the stream.
/// null = marker must be written/read (bool, enum, string, object, collection, nullable value types).
/// Non-null = markerless (non-nullable value types: int, long, double, float, decimal, Guid, DateTime, byte, short, ushort, uint, ulong).
/// </summary>
public byte? ExpectedTypeCode { get; }
protected BinaryPropertyAccessorBase(PropertyInfo prop, Type declaringType)
: base(prop, declaringType)
{
IsStringCollectionProperty = IsStringCollection(prop.PropertyType);
// All typed getters are initialized in PropertyAccessorBase
if (AccessorType == PropertyAccessorType.String || IsStringCollectionProperty)
{
// Cache [AcStringIntern] attribute (inherit: true to check base class properties)
var internAttr = prop.GetCustomAttribute<AcStringInternAttribute>(inherit: true);
var stringInternAttributeValue = internAttr?.Enabled;
byte flags = 0;
if (stringInternAttributeValue == true) flags |= (1 << (int)StringInterningMode.Attribute);
if (stringInternAttributeValue != false) flags |= (1 << (int)StringInterningMode.All);
_interningFlags = flags;
}
ExpectedTypeCode = ComputeExpectedTypeCode(AccessorType);
}
/// <summary>
/// Checks if the property type is a collection with string elements (string[], List&lt;string&gt;, etc.).
/// </summary>
private static bool IsStringCollection(Type propertyType)
{
if (propertyType.IsArray)
return propertyType.GetElementType() == typeof(string);
if (propertyType.IsGenericType)
{
var args = propertyType.GetGenericArguments();
return args.Length == 1 && args[0] == typeof(string);
}
return false;
}
/// <summary>
/// Maps AccessorType to the BinaryTypeCode that would normally be written as marker.
/// Returns null for types that always need a stream marker (string, object/nullable).
/// </summary>
private static byte? ComputeExpectedTypeCode(PropertyAccessorType accessorType) => accessorType switch
{
PropertyAccessorType.Int32 => BinaryTypeCode.Int32,
PropertyAccessorType.Int64 => BinaryTypeCode.Int64,
PropertyAccessorType.Double => BinaryTypeCode.Float64,
PropertyAccessorType.Single => BinaryTypeCode.Float32,
PropertyAccessorType.Decimal => BinaryTypeCode.Decimal,
PropertyAccessorType.DateTime => BinaryTypeCode.DateTime,
PropertyAccessorType.Guid => BinaryTypeCode.Guid,
PropertyAccessorType.Byte => BinaryTypeCode.UInt8,
PropertyAccessorType.Int16 => BinaryTypeCode.Int16,
PropertyAccessorType.UInt16 => BinaryTypeCode.UInt16,
PropertyAccessorType.UInt32 => BinaryTypeCode.UInt32,
PropertyAccessorType.UInt64 => BinaryTypeCode.UInt64,
PropertyAccessorType.Boolean => BinaryTypeCode.True,
PropertyAccessorType.Enum => BinaryTypeCode.Enum,
_ => null // String, Object — always write marker to stream
};
}