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