diff --git a/AyCode.Core/Serializers/AcSerializerCommon.cs b/AyCode.Core/Serializers/AcSerializerCommon.cs index 1e50ad2..2351b6a 100644 --- a/AyCode.Core/Serializers/AcSerializerCommon.cs +++ b/AyCode.Core/Serializers/AcSerializerCommon.cs @@ -643,7 +643,11 @@ public static class AcSerializerCommon { return _tracked.TryAdd(key, null); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool HasKey(TId key) => _tracked.ContainsKey(key); + /// /// Checks if key exists and returns existing value, or adds new key (deserialization). /// Returns true if first occurrence (key was added, out = null). @@ -669,7 +673,7 @@ public static class AcSerializerCommon slot = newValue; return newValue; } - + /// /// Tries to get the value for a key (ObjectRef lookup). /// Returns true if found, false if not. diff --git a/AyCode.Core/Serializers/AcSerializerContextBase.cs b/AyCode.Core/Serializers/AcSerializerContextBase.cs index f84ea3f..b464d9f 100644 --- a/AyCode.Core/Serializers/AcSerializerContextBase.cs +++ b/AyCode.Core/Serializers/AcSerializerContextBase.cs @@ -53,7 +53,7 @@ public abstract class AcSerializerContextBase where TMetadata : TypeM { // Get metadata from global cache (thread-safe) var metadata = GlobalMetadataCache.GetOrAdd(type, MetadataFactory); - + // Create wrapper with metadata + tracking state (per-context) var wrapper = new TypeMetadataWrapper(metadata); _wrappers[type] = wrapper; @@ -64,6 +64,45 @@ public abstract class AcSerializerContextBase where TMetadata : TypeM #region Tracking API - int + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetValue(TypeMetadataWrapper wrapper, int refId, out object? instance) + { + var map = wrapper.IdentityMap as AcSerializerCommon.IdentityMap; + if (map == null) + { + map = new AcSerializerCommon.IdentityMap(); + wrapper.IdentityMap = map; + } + + return map.TryGetValue(refId, out instance); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetValue(TypeMetadataWrapper wrapper, long refId, out object? instance) + { + var map = wrapper.IdentityMap as AcSerializerCommon.IdentityMap; + if (map == null) + { + map = new AcSerializerCommon.IdentityMap(); + wrapper.IdentityMap = map; + } + + return map.TryGetValue(refId, out instance); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetValue(TypeMetadataWrapper wrapper, Guid refId, out object? instance) + { + var map = wrapper.IdentityMap as AcSerializerCommon.IdentityMap; + if (map == null) + { + map = new AcSerializerCommon.IdentityMap(); + wrapper.IdentityMap = map; + } + + return map.TryGetValue(refId, out instance); + } + /// /// Tries to track an object with int RefId. /// Use when wrapper.Metadata.IdAccessorType == Int32. @@ -78,10 +117,10 @@ public abstract class AcSerializerContextBase where TMetadata : TypeM refId = getter(obj); // BitArray fast path for small positive IDs - if (refId >= 0 && refId < MaxSmallId) - { - return TryTrackSmallId(wrapper, refId); - } + //if (refId >= 0 && refId < MaxSmallId) + //{ + // return TryTrackSmallId(wrapper, refId); + //} // IdentityMap for large/negative IDs var map = wrapper.IdentityMap as AcSerializerCommon.IdentityMap; diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.cs index e47603c..3c09da7 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.cs @@ -19,7 +19,7 @@ public static partial class AcBinaryDeserializer private int _position; private List? _internedStrings; private List? _propertyNames; - private Dictionary? _objectReferences; + //private Dictionary? _objectReferences; private Dictionary? _stringCache; private readonly byte _minStringInternLength; private readonly bool _useStringCaching; @@ -66,7 +66,7 @@ public static partial class AcBinaryDeserializer _position = 0; _internedStrings = null; _propertyNames = null; - _objectReferences = null; + //_objectReferences = null; _stringCache = null; HasMetadata = false; HasReferenceHandling = false; @@ -522,33 +522,35 @@ public static partial class AcBinaryDeserializer } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RegisterObject(int refId, object instance) + public void RegisterObject(TypeMetadataWrapper wrapper, int refId, object instance) { - if (refId <= 0) - { - return; - } + if (refId == 0) throw new Exception("refId == 0"); + ContextClass.TryGetOrStoreInt32(wrapper, instance, refId); + //if (refId <= 0) + //{ + // return; + //} - _objectReferences ??= new Dictionary(16); - _objectReferences[refId] = instance; + //_objectReferences ??= new Dictionary(16); + //_objectReferences[refId] = instance; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public object? GetReferencedObject(int refId) - { - if (refId <= 0) - { - return null; - } + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + //public object? GetReferencedObject(TypeMetadataWrapper wrapper, int refId) + //{ + // //if (refId <= 0) + // //{ + // // return null; + // //} - if (_objectReferences == null || !_objectReferences.TryGetValue(refId, out var value)) - { - throw new AcBinaryDeserializationException($"Unknown object reference id '{refId}'.", _position); - } + // //if (_objectReferences == null || !_objectReferences.TryGetValue(refId, out var value)) + // //{ + // // throw new AcBinaryDeserializationException($"Unknown object reference id '{refId}'.", _position); + // //} - return value; - } + // //return value; + //} private void EnsureAvailable(int length) { @@ -563,20 +565,20 @@ public static partial class AcBinaryDeserializer var byteLength = (int)ReadVarUInt(); return ReadStringUtf8(byteLength); } - + #region IId Reference Cache - Delegates to ContextClass - + /// /// After PopulateObject, checks if we should reuse an existing IId object. /// Delegates to ContextClass which uses AcSerializerContextBase infrastructure. /// Returns the object to use (either the new one or an existing cached one). /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public object GetOrRegisterIIdObject(object newObj, BinaryDeserializeTypeMetadata metadata) - { - return ContextClass.GetOrRegisterIIdObject(newObj, metadata); - } - + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + //public object GetOrRegisterIIdObject(object newObj, TypeMetadataWrapper wrapper) + //{ + // return ContextClass.GetOrRegisterIIdObject(newObj, wrapper); + //} + #endregion } } diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.CrossType.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.CrossType.cs index 3069eec..4af38f9 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.CrossType.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.CrossType.cs @@ -215,23 +215,20 @@ public static partial class AcBinaryDeserializer /// private static object? ReadObjectWithMapping(ref BinaryDeserializationContext context, Type destType, int[] indexMapping, int depth) { - var metadata = GetTypeMetadata(destType); + var wrapper = context.ContextClass.GetWrapper(destType); + var metadata = wrapper.Metadata; // Handle reference ID if present if (context.HasReferenceHandling) { var refId = context.ReadVarInt(); - if (refId < 0) - { - // Object reference - get existing object - return context.GetReferencedObject(-refId); - } - + if (context.ContextClass.TryGetValue(wrapper, refId, out var instance)) return instance; + // New object with ID - will be registered below - var instance = CreateInstance(destType, metadata); + instance = CreateInstance(destType, metadata); if (instance != null && refId > 0) { - context.RegisterObject(refId, instance); + context.RegisterObject(wrapper, refId, instance); } PopulateObjectWithMapping(ref context, instance!, destType, indexMapping, depth); @@ -258,7 +255,8 @@ public static partial class AcBinaryDeserializer int[] indexMapping, int depth) { - var metadata = GetTypeMetadata(destType); + var wrapper = context.ContextClass.GetWrapper(destType); + var metadata = wrapper.Metadata; var propertyCount = (int)context.ReadVarUInt(); var nextDepth = depth + 1; @@ -286,7 +284,7 @@ public static partial class AcBinaryDeserializer } // Reuse common populate logic - PopulatePropertyValue(ref context, target, propInfo, nextDepth, sourcePropIndex, destPropIndex, i, propertyCount, depth); + PopulatePropertyValue(ref context, target, propInfo, wrapper, nextDepth, sourcePropIndex, destPropIndex, i, propertyCount, depth); } } @@ -311,6 +309,7 @@ public static partial class AcBinaryDeserializer ref BinaryDeserializationContext context, object target, BinaryPropertySetterInfo propInfo, + TypeMetadataWrapper wrapper, int nextDepth, int sourcePropIndex, int destPropIndex, @@ -342,11 +341,11 @@ public static partial class AcBinaryDeserializer var refId = context.ReadVarInt(); if (refId > 0) { - context.RegisterObject(refId, existingObj); + context.RegisterObject(wrapper, refId, existingObj); } } - PopulateObjectCore(ref context, existingObj, GetTypeMetadata(propInfo.PropertyType), nextDepth, skipDefaultWrite: false); + PopulateObjectCore(ref context, existingObj, wrapper.Metadata, nextDepth, skipDefaultWrite: false); return; } } diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.Populate.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.Populate.cs index 3846ef5..f76f7f3 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.Populate.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.Populate.cs @@ -12,13 +12,18 @@ public static partial class AcBinaryDeserializer { #region Populate Object Methods + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static BinaryDeserializeTypeMetadata GetTypeMetadata(Type type) + => null;//MetadataCache.GetOrAdd(type, static t => new BinaryDeserializeTypeMetadata(t)); + /// /// Populate object with automatic mode detection from context. /// Uses IsMergeMode to determine merge behavior for IId collections. /// private static void PopulateObject(ref BinaryDeserializationContext context, object target, Type targetType, int depth) { - var metadata = GetTypeMetadata(targetType); + var wrapper = context.ContextClass.GetWrapper(targetType); + var metadata = wrapper.Metadata; // Handle ref ID if present if (context.HasReferenceHandling) @@ -26,7 +31,7 @@ public static partial class AcBinaryDeserializer var refId = context.ReadVarInt(); if (refId > 0) { - context.RegisterObject(refId, target); + context.RegisterObject(wrapper, refId, target); } } @@ -105,6 +110,8 @@ public static partial class AcBinaryDeserializer var existingObj = propInfo.GetValue(target); if (existingObj != null) { + var wrapper = context.ContextClass.GetWrapper(propInfo.PropertyType); + context.ReadByte(); // consume Object marker // Handle ref ID if present @@ -113,12 +120,12 @@ public static partial class AcBinaryDeserializer var refId = context.ReadVarInt(); if (refId > 0) { - context.RegisterObject(refId, existingObj); + context.RegisterObject(wrapper, refId, existingObj); } } // Recursively populate - existing object, don't skip defaults - PopulateObjectCore(ref context, existingObj, GetTypeMetadata(propInfo.PropertyType), nextDepth, skipDefaultWrite: false); + PopulateObjectCore(ref context, existingObj, wrapper.Metadata, nextDepth, skipDefaultWrite: false); continue; } } @@ -207,8 +214,10 @@ public static partial class AcBinaryDeserializer try { + var wrapper = context.ContextClass.GetWrapper(elementType); var existingCount = existingList.Count; - var elementMetadata = IsComplexType(elementType) ? GetTypeMetadata(elementType) : null; + + var elementMetadata = wrapper.Metadata.IsComplexType ? wrapper.Metadata : null; for (int i = 0; i < count; i++) { @@ -228,7 +237,7 @@ public static partial class AcBinaryDeserializer var refId = context.ReadVarInt(); if (refId > 0) { - context.RegisterObject(refId, existingItem); + context.RegisterObject(wrapper, refId, existingItem); } } @@ -302,7 +311,8 @@ public static partial class AcBinaryDeserializer var arrayCount = (int)context.ReadVarUInt(); var nextDepth = depth + 1; - var elementMetadata = GetTypeMetadata(elementType); + var wrapper = context.ContextClass.GetWrapper(elementType); + var elementMetadata = wrapper.Metadata; // Track which IDs we see in source (for orphan removal) HashSet? sourceIds = context.RemoveOrphanedItems && existingById != null @@ -329,7 +339,7 @@ public static partial class AcBinaryDeserializer { var refId = context.ReadVarInt(); if (refId > 0) - context.RegisterObject(refId, newItem); + context.RegisterObject(wrapper, refId, newItem); } // Deserialize mode: new item just created, skip writing defaults @@ -383,9 +393,10 @@ public static partial class AcBinaryDeserializer ref BinaryDeserializationContext context, IList existingList, Type elementType, - BinaryDeserializeTypeMetadata elementMetadata, + TypeMetadataWrapper wrapper, int depth) { + var elementMetadata = wrapper.Metadata; var idGetter = elementMetadata.IdGetter!; var idType = elementMetadata.IdType!; @@ -440,7 +451,7 @@ public static partial class AcBinaryDeserializer { var refId = context.ReadVarInt(); if (refId > 0) - context.RegisterObject(refId, newItem); + context.RegisterObject(wrapper, refId, newItem); } // Deserialize mode: new item just created, skip writing defaults @@ -546,7 +557,7 @@ public static partial class AcBinaryDeserializer /// Determines if a type is a complex type (not primitive, string, or simple value type). /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsComplexType(Type type) + private static bool IsComplexType5(Type type) { if (type.IsPrimitive) return false; if (ReferenceEquals(type, StringType)) return false; diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs index 74149c6..95b56d5 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs @@ -325,15 +325,18 @@ public static partial class AcBinaryDeserializer context.ReadByte(); // For top-level list merge, check if it's an IId collection var elementType = GetCollectionElementType(targetType); - if (elementType != null && IsComplexType(elementType)) + if (elementType != null) { - var elementMetadata = GetTypeMetadata(elementType); - if (elementMetadata.IsIId && elementMetadata.IdGetter != null) + var wrapper = context.ContextClass.GetWrapper(elementType); + var elementMetadata = wrapper.Metadata; + + if (elementMetadata.IsComplexType && elementMetadata.IsIId && elementMetadata.IdGetter != null) { - MergeIIdCollectionWithMetadata(ref context, targetList, elementType, elementMetadata, 0); + MergeIIdCollectionWithMetadata(ref context, targetList, elementType, wrapper, 0); return; } } + // Non-IId collection, just populate PopulateList(ref context, targetList, targetType, 0); } @@ -909,11 +912,11 @@ public static partial class AcBinaryDeserializer /// private static object? ReadObjectRef(ref BinaryDeserializationContext context, Type targetType, int depth) { - var metadata = GetTypeMetadata(targetType); + var wrapper = context.ContextClass.GetWrapper(targetType); + var metadata = wrapper.Metadata; - if (metadata.IsIId) + if (metadata.IdAccessorType != AcSerializerCommon.IdAccessorType.None) { - var wrapper = context.ContextClass.GetWrapper(targetType); var map = wrapper.IdentityMap; // Read ID based on type and lookup in IdentityMap @@ -922,12 +925,11 @@ public static partial class AcBinaryDeserializer AcSerializerCommon.IdAccessorType.Int32 => ReadObjectRefInt32(ref context, map), AcSerializerCommon.IdAccessorType.Int64 => ReadObjectRefInt64(ref context, map), AcSerializerCommon.IdAccessorType.Guid => ReadObjectRefGuid(ref context, map), - _ => context.GetReferencedObject(context.ReadVarInt()) + _ => throw new Exception("metadata.IdAccessorType not valid") }; } - // Non-IId: sequential int refId - return context.GetReferencedObject(context.ReadVarInt()); + return null; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -936,7 +938,7 @@ public static partial class AcBinaryDeserializer var id = context.ReadVarInt(); if (map is AcSerializerCommon.IdentityMap intMap && intMap.TryGetValue(id, out var obj)) return obj; - return context.GetReferencedObject(id); + return null; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -959,36 +961,53 @@ public static partial class AcBinaryDeserializer private static object? ReadObject(ref BinaryDeserializationContext context, Type targetType, int depth) { - var metadata = GetTypeMetadata(targetType); - - // Read reference ID based on IdAccessorType (different wire format for each type) - object? readId = null; - if (context.HasReferenceHandling) - { - readId = metadata.IdAccessorType switch - { - AcSerializerCommon.IdAccessorType.Int32 => context.ReadVarInt(), - AcSerializerCommon.IdAccessorType.Int64 => context.ReadVarLong(), - AcSerializerCommon.IdAccessorType.Guid => context.ReadGuidUnsafe(), - _ => throw new Exception($"metadata.IdAccessorType not valid: {metadata.IdAccessorType}") - }; - } - // Handle dictionary types if (IsDictionaryType(targetType, out var keyType, out var valueType)) { return ReadDictionaryAsObject(ref context, keyType!, valueType!, depth); } + + var wrapper = context.ContextClass.GetWrapper(targetType); + var metadata = wrapper.Metadata; - // Create instance - var instance = CreateInstance(targetType, metadata); - if (instance == null) return null; + object? instance = null; - // Register reference for flat lookup (int refId only) - if (readId is int intRefId) + if (context.HasReferenceHandling && metadata.IdAccessorType != AcSerializerCommon.IdAccessorType.None) { - if (intRefId == 0) throw new Exception("intRefId == 0"); - context.RegisterObject(intRefId, instance); + switch (metadata.IdAccessorType) + { + case AcSerializerCommon.IdAccessorType.Int32: + var intId = context.ReadVarInt(); + if (context.ContextClass.TryGetValue(wrapper, intId, out instance)) return instance; + + instance = CreateInstance(targetType, metadata); + if (instance == null) return null; + context.ContextClass.TryGetOrStoreInt32(wrapper, instance, intId); + break; + case AcSerializerCommon.IdAccessorType.Int64: + var longId = context.ReadVarLong(); + if (context.ContextClass.TryGetValue(wrapper, longId, out instance)) return instance; + + instance = CreateInstance(targetType, metadata); + if (instance == null) return null; + context.ContextClass.TryGetOrStoreLong(wrapper, instance, longId); + break; + case AcSerializerCommon.IdAccessorType.Guid: + var guidId = context.ReadGuidUnsafe(); + if (context.ContextClass.TryGetValue(wrapper, guidId, out instance)) return instance; + + instance = CreateInstance(targetType, metadata); + if (instance == null) return null; + context.ContextClass.TryGetOrStoreGuid(wrapper, instance, guidId); + break; + default: + throw new Exception($"metadata.IdAccessorType not valid: {metadata.IdAccessorType}"); + } + } + else + { + instance = CreateInstance(targetType, metadata); + if (instance == null) return null; } // Deserialize mode: object just created, skip writing defaults (already at default) @@ -998,14 +1017,23 @@ public static partial class AcBinaryDeserializer // Note: For ChainMode, we need IdGetter OR typed getters (IdAccessorType != None) if (context.IsChainMode && metadata.IsIId && metadata.IdType != null) { - object? id = metadata.IdAccessorType switch + object? id; + switch (metadata.IdAccessorType) { - AcSerializerCommon.IdAccessorType.Int32 => metadata.GetIdInt32(instance), - AcSerializerCommon.IdAccessorType.Int64 => metadata.GetIdInt64(instance), - AcSerializerCommon.IdAccessorType.Guid => metadata.GetIdGuid(instance), - _ => null - }; - + case AcSerializerCommon.IdAccessorType.Int32: + id = metadata.GetIdInt32(instance); + break; + case AcSerializerCommon.IdAccessorType.Int64: + id = metadata.GetIdInt64(instance); + break; + case AcSerializerCommon.IdAccessorType.Guid: + id = metadata.GetIdGuid(instance); + break; + default: + id = null; + break; + } + if (id != null && !IsDefaultValue(id, metadata.IdType)) { // Check if we already have this object @@ -1020,11 +1048,6 @@ public static partial class AcBinaryDeserializer context.ChainTracker.TryRegisterIIdObject(instance); } } - // Normal IId cache for non-chain deserialization - else if (context.HasReferenceHandling && metadata.IsIId) - { - instance = context.GetOrRegisterIIdObject(instance, metadata); - } return instance; } @@ -1424,13 +1447,6 @@ public static partial class AcBinaryDeserializer #region Type Metadata - // Temporary: own cache until ref struct is removed - private static readonly ConcurrentDictionary MetadataCache = new(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static BinaryDeserializeTypeMetadata GetTypeMetadata(Type type) - => MetadataCache.GetOrAdd(type, static t => new BinaryDeserializeTypeMetadata(t)); - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static object? CreateInstance(Type type, BinaryDeserializeTypeMetadata metadata) { diff --git a/AyCode.Core/Serializers/TypeMetadataBase.cs b/AyCode.Core/Serializers/TypeMetadataBase.cs index 31b6ac1..a44d35b 100644 --- a/AyCode.Core/Serializers/TypeMetadataBase.cs +++ b/AyCode.Core/Serializers/TypeMetadataBase.cs @@ -102,8 +102,9 @@ public abstract class TypeMetadataBase /// /// Typed getter delegate for IId.Id property. /// Type depends on IdAccessorType (Func<object, int>, Func<object, long>, or Func<object, Guid>). + /// Cached here to avoid creating new delegates per wrapper. /// - private readonly Delegate? _typedIdGetter; + public Delegate? TypedIdGetter { get; } #region Scan Optimization Flags @@ -112,7 +113,9 @@ public abstract class TypeMetadataBase /// If false, ScanReferences can skip child property scanning entirely. /// public bool HasComplexProperties { get; protected set; } - + + public bool IsComplexType { get; init; } + /// /// True if this type or any of its properties could potentially be shared (multi-referenced). /// False for sealed value-like types with only primitives - these never need reference tracking. @@ -125,19 +128,19 @@ public abstract class TypeMetadataBase /// Gets the Id as int without boxing. Only valid when IdAccessorType == Int32. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int GetIdInt32(object obj) => ((Func)_typedIdGetter!)(obj); + public int GetIdInt32(object obj) => ((Func)TypedIdGetter!)(obj); /// /// Gets the Id as long without boxing. Only valid when IdAccessorType == Int64. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public long GetIdInt64(object obj) => ((Func)_typedIdGetter!)(obj); + public long GetIdInt64(object obj) => ((Func)TypedIdGetter!)(obj); /// /// Gets the Id as Guid without boxing. Only valid when IdAccessorType == Guid. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Guid GetIdGuid(object obj) => ((Func)_typedIdGetter!)(obj); + public Guid GetIdGuid(object obj) => ((Func)TypedIdGetter!)(obj); protected TypeMetadataBase(Type type, Func ignorePropertyFilter) { @@ -153,11 +156,14 @@ public abstract class TypeMetadataBase ReadableProperties = allReadable; WritableProperties = allReadable.Where(p => p.CanWrite).ToArray(); + IsComplexType = IsComplexType2(type); + // Cache IId info at construction time - no runtime reflection needed later! var idInfo = GetIdInfo(type); IsIId = idInfo.IsId; IdType = idInfo.IdType; + if (IsIId) { var idProp = type.GetProperty("Id"); @@ -167,17 +173,17 @@ public abstract class TypeMetadataBase if (ReferenceEquals(IdType, IntType)) { IdAccessorType = AcSerializerCommon.IdAccessorType.Int32; - _typedIdGetter = AcSerializerCommon.CreateTypedGetter(type, idProp); + TypedIdGetter = AcSerializerCommon.CreateTypedGetter(type, idProp); } else if (ReferenceEquals(IdType, LongType)) { IdAccessorType = AcSerializerCommon.IdAccessorType.Int64; - _typedIdGetter = AcSerializerCommon.CreateTypedGetter(type, idProp); + TypedIdGetter = AcSerializerCommon.CreateTypedGetter(type, idProp); } else if (ReferenceEquals(IdType, GuidType)) { IdAccessorType = AcSerializerCommon.IdAccessorType.Guid; - _typedIdGetter = AcSerializerCommon.CreateTypedGetter(type, idProp); + TypedIdGetter = AcSerializerCommon.CreateTypedGetter(type, idProp); } else { @@ -189,7 +195,7 @@ public abstract class TypeMetadataBase // Non-IId types: use RuntimeHelpers.GetHashCode (int) // RefIdGetter is created in TypeMetadataWrapper.CreateRefIdGetter() IdAccessorType = AcSerializerCommon.IdAccessorType.Int32; - // _typedIdGetter remains null - wrapper uses GetHashCode directly + // TypedIdGetter remains null - wrapper uses GetHashCode directly } } @@ -241,4 +247,19 @@ public abstract class TypeMetadataBase return allProperties; }); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsComplexType2(Type type) + { + if (type.IsPrimitive) return false; + if (ReferenceEquals(type, StringType)) return false; + if (type.IsEnum) return false; + if (ReferenceEquals(type, GuidType)) return false; + if (ReferenceEquals(type, DateTimeType)) return false; + if (ReferenceEquals(type, DecimalType)) return false; + if (ReferenceEquals(type, TimeSpanType)) return false; + if (ReferenceEquals(type, DateTimeOffsetType)) return false; + if (Nullable.GetUnderlyingType(type) != null) return false; + return true; + } } diff --git a/AyCode.Core/Serializers/TypeMetadataWrapper.cs b/AyCode.Core/Serializers/TypeMetadataWrapper.cs index 31d8e36..6e5b1ee 100644 --- a/AyCode.Core/Serializers/TypeMetadataWrapper.cs +++ b/AyCode.Core/Serializers/TypeMetadataWrapper.cs @@ -20,6 +20,8 @@ public sealed class TypeMetadataWrapper where TMetadata : TypeMetadat /// /// Typed getter for reference ID. Runtime type is Func<object, int/long/Guid>. /// Use IdAccessorType to determine the actual type. + /// For IId types: uses metadata.TypedIdGetter (cached in metadata). + /// For non-IId types: uses RuntimeHelpers.GetHashCode. /// internal readonly Delegate RefIdGetter; @@ -36,39 +38,22 @@ public sealed class TypeMetadataWrapper where TMetadata : TypeMetadat private const int BitArraySize = 1024; // 1024 * 64 = 65,536 IDs + /// + /// Static fallback delegate for non-IId types. + /// Shared across all wrappers to avoid allocation. + /// + private static readonly Func HashCodeGetter = RuntimeHelpers.GetHashCode; + /// /// Creates a new wrapper for the given metadata. - /// Initializes RefIdGetter based on IdAccessorType. + /// Uses metadata.TypedIdGetter for IId types (cached, no allocation). /// public TypeMetadataWrapper(TMetadata metadata) { Metadata = metadata; - // Create typed RefIdGetter based on IdAccessorType - RefIdGetter = CreateRefIdGetter(metadata); - } - - private static Delegate CreateRefIdGetter(TMetadata metadata) - { - if (metadata.IsIId && metadata.IdPropertyInfo != null) - { - // IId type - create typed getter from Id property - return metadata.IdAccessorType switch - { - AcSerializerCommon.IdAccessorType.Int32 => - AcSerializerCommon.CreateTypedGetter(metadata.MetadataType, metadata.IdPropertyInfo), - AcSerializerCommon.IdAccessorType.Int64 => - AcSerializerCommon.CreateTypedGetter(metadata.MetadataType, metadata.IdPropertyInfo), - AcSerializerCommon.IdAccessorType.Guid => - AcSerializerCommon.CreateTypedGetter(metadata.MetadataType, metadata.IdPropertyInfo), - _ => throw new NotSupportedException($"Unsupported IdAccessorType: {metadata.IdAccessorType}") - }; - } - else - { - // Non-IId type - use RuntimeHelpers.GetHashCode - return new Func(RuntimeHelpers.GetHashCode); - } + // Use cached delegate from metadata for IId types, static fallback for non-IId + RefIdGetter = metadata.TypedIdGetter ?? HashCodeGetter; } ///