118 lines
5.1 KiB
C#
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<string>, 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
|
|
};
|
|
}
|