Feature flags for serialization: fine-grained control
AcBinarySourceGenerator now reads feature flags from [AcBinarySerializable], enabling selective code generation for ID tracking, reference handling, and string interning. Property ordering is always alphabetical, removing "Id"-first sorting for IId types. Reference tracking code is only emitted when features are enabled. TypeMetadataBase and AcBinarySerializer runtime logic now respect these flags. Default options updated: ReferenceHandlingMode is All, UseMetadata is false. Test models explicitly disable all features. Comments and code structure improved for clarity.
This commit is contained in:
parent
77ea512c1f
commit
dcd9783b3b
|
|
@ -41,6 +41,33 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
: typeSymbol.ContainingNamespace.ToDisplayString();
|
: typeSymbol.ContainingNamespace.ToDisplayString();
|
||||||
|
|
||||||
var properties = new List<PropInfo>();
|
var properties = new List<PropInfo>();
|
||||||
|
|
||||||
|
// Read feature flags from [AcBinarySerializable] — disabled features eliminate
|
||||||
|
// corresponding code blocks from generated ScanObject/WriteProperties.
|
||||||
|
var enableIdTracking = true;
|
||||||
|
var enableRefHandling = true;
|
||||||
|
var enableInternString = true;
|
||||||
|
var binarySerializableAttr = typeSymbol.GetAttributes().FirstOrDefault(a =>
|
||||||
|
a.AttributeClass?.ToDisplayString() == AttributeName);
|
||||||
|
if (binarySerializableAttr != null)
|
||||||
|
{
|
||||||
|
if (binarySerializableAttr.ConstructorArguments.Length == 1)
|
||||||
|
{
|
||||||
|
// Single bool ctor: AcBinarySerializable(enableAllFeatures)
|
||||||
|
var all = (bool)binarySerializableAttr.ConstructorArguments[0].Value!;
|
||||||
|
enableIdTracking = all;
|
||||||
|
enableRefHandling = all;
|
||||||
|
enableInternString = all;
|
||||||
|
}
|
||||||
|
else if (binarySerializableAttr.ConstructorArguments.Length == 4)
|
||||||
|
{
|
||||||
|
// Four bool ctor: (metadata, idTracking, refHandling, internString)
|
||||||
|
enableIdTracking = (bool)binarySerializableAttr.ConstructorArguments[1].Value!;
|
||||||
|
enableRefHandling = (bool)binarySerializableAttr.ConstructorArguments[2].Value!;
|
||||||
|
enableInternString = (bool)binarySerializableAttr.ConstructorArguments[3].Value!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var member in typeSymbol.GetMembers())
|
foreach (var member in typeSymbol.GetMembers())
|
||||||
{
|
{
|
||||||
if (member is IPropertySymbol p &&
|
if (member is IPropertySymbol p &&
|
||||||
|
|
@ -57,7 +84,11 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
|
|
||||||
// String interning attribútum detektálás (null = no attr, true/false = explicit)
|
// String interning attribútum detektálás (null = no attr, true/false = explicit)
|
||||||
bool? stringInternAttr = null;
|
bool? stringInternAttr = null;
|
||||||
if (GetKind(p.Type) == PropertyTypeKind.String)
|
if (!enableInternString)
|
||||||
|
{
|
||||||
|
stringInternAttr = false;
|
||||||
|
}
|
||||||
|
else if (GetKind(p.Type) == PropertyTypeKind.String)
|
||||||
{
|
{
|
||||||
var attr = p.GetAttributes().FirstOrDefault(a => a.AttributeClass?.ToDisplayString() == "AyCode.Core.Serializers.Binaries.AcStringInternAttribute");
|
var attr = p.GetAttributes().FirstOrDefault(a => a.AttributeClass?.ToDisplayString() == "AyCode.Core.Serializers.Binaries.AcStringInternAttribute");
|
||||||
if (attr != null && attr.ConstructorArguments.Length == 1 && attr.ConstructorArguments[0].Kind == TypedConstantKind.Primitive)
|
if (attr != null && attr.ConstructorArguments.Length == 1 && attr.ConstructorArguments[0].Kind == TypedConstantKind.Primitive)
|
||||||
|
|
@ -202,8 +233,11 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
}
|
}
|
||||||
|
|
||||||
// IId<T>: Id first (index 0), then alphabetical — matches runtime TypeMetadataBase ordering
|
// IId<T>: Id first (index 0), then alphabetical — matches runtime TypeMetadataBase ordering
|
||||||
|
// If EnableIdTrackingFeature == false, skip IId detection entirely → isIId = false
|
||||||
var isIId = false;
|
var isIId = false;
|
||||||
string? idTypeName = null;
|
string? idTypeName = null;
|
||||||
|
if (enableIdTracking)
|
||||||
|
{
|
||||||
var iidInterface = typeSymbol.AllInterfaces.FirstOrDefault(i =>
|
var iidInterface = typeSymbol.AllInterfaces.FirstOrDefault(i =>
|
||||||
i.IsGenericType &&
|
i.IsGenericType &&
|
||||||
i.OriginalDefinition.ToDisplayString() == "AyCode.Core.Interfaces.IId<T>");
|
i.OriginalDefinition.ToDisplayString() == "AyCode.Core.Interfaces.IId<T>");
|
||||||
|
|
@ -212,22 +246,14 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
isIId = true;
|
isIId = true;
|
||||||
idTypeName = iidInterface.TypeArguments[0].ToDisplayString();
|
idTypeName = iidInterface.TypeArguments[0].ToDisplayString();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isIId)
|
|
||||||
properties.Sort((a, b) =>
|
|
||||||
{
|
|
||||||
var aIsId = a.Name == "Id" ? 0 : 1;
|
|
||||||
var bIsId = b.Name == "Id" ? 0 : 1;
|
|
||||||
if (aIsId != bIsId) return aIsId.CompareTo(bIsId);
|
|
||||||
return string.Compare(a.Name, b.Name, StringComparison.Ordinal);
|
|
||||||
});
|
|
||||||
else
|
|
||||||
properties.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
|
properties.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
|
||||||
|
|
||||||
var className = BuildFlatName(typeSymbol);
|
var className = BuildFlatName(typeSymbol);
|
||||||
var typeNameHash = ComputeFnvHash(typeSymbol.Name);
|
var typeNameHash = ComputeFnvHash(typeSymbol.Name);
|
||||||
var propertyNameHashes = properties.Select(prop => ComputeFnvHash(prop.Name)).ToArray();
|
var propertyNameHashes = properties.Select(prop => ComputeFnvHash(prop.Name)).ToArray();
|
||||||
return new SerializableClassInfo(namespaceName, className, typeSymbol.ToDisplayString(), properties, isIId, idTypeName, typeNameHash, propertyNameHashes);
|
return new SerializableClassInfo(namespaceName, className, typeSymbol.ToDisplayString(), properties, isIId, idTypeName, enableRefHandling, typeNameHash, propertyNameHashes);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Execute(ImmutableArray<SerializableClassInfo?> classes, SourceProductionContext context)
|
private static void Execute(ImmutableArray<SerializableClassInfo?> classes, SourceProductionContext context)
|
||||||
|
|
@ -259,6 +285,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
sb.AppendLine("{");
|
sb.AppendLine("{");
|
||||||
sb.AppendLine($" internal static readonly {ci.ClassName}_GeneratedWriter Instance = new();");
|
sb.AppendLine($" internal static readonly {ci.ClassName}_GeneratedWriter Instance = new();");
|
||||||
sb.AppendLine($" internal static readonly int s_metadataSlot = AcBinarySerializer.AllocateMetadataSlot();");
|
sb.AppendLine($" internal static readonly int s_metadataSlot = AcBinarySerializer.AllocateMetadataSlot();");
|
||||||
|
if (ci.IsIId || ci.EnableRefHandling)
|
||||||
sb.AppendLine($" internal static readonly int s_trackingSlot = AcBinarySerializer.AllocateTrackingSlot();");
|
sb.AppendLine($" internal static readonly int s_trackingSlot = AcBinarySerializer.AllocateTrackingSlot();");
|
||||||
sb.AppendLine($" internal static readonly int s_typeNameHash = {ci.TypeNameHash};");
|
sb.AppendLine($" internal static readonly int s_typeNameHash = {ci.TypeNameHash};");
|
||||||
sb.Append( $" internal static readonly int[] s_propertyHashes = new int[] {{ ");
|
sb.Append( $" internal static readonly int[] s_propertyHashes = new int[] {{ ");
|
||||||
|
|
@ -312,6 +339,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
sb.AppendLine($" var obj = Unsafe.As<{ci.FullTypeName}>(value);");
|
sb.AppendLine($" var obj = Unsafe.As<{ci.FullTypeName}>(value);");
|
||||||
|
|
||||||
// Self ref tracking — matches runtime ScanValue UseTypeReferenceHandling block
|
// Self ref tracking — matches runtime ScanValue UseTypeReferenceHandling block
|
||||||
|
// Only emitted when the corresponding feature flag is enabled.
|
||||||
if (ci.IsIId)
|
if (ci.IsIId)
|
||||||
{
|
{
|
||||||
// IId type: track when ReferenceHandling != None
|
// IId type: track when ReferenceHandling != None
|
||||||
|
|
@ -329,7 +357,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
sb.AppendLine(" return;");
|
sb.AppendLine(" return;");
|
||||||
sb.AppendLine(" }");
|
sb.AppendLine(" }");
|
||||||
}
|
}
|
||||||
else
|
else if (ci.EnableRefHandling)
|
||||||
{
|
{
|
||||||
// Non-IId type: track when ReferenceHandling == All
|
// Non-IId type: track when ReferenceHandling == All
|
||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
|
|
@ -1065,7 +1093,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Computes FNV-1a hashes for all serializable properties of a child type.
|
/// Computes FNV-1a hashes for all serializable properties of a child type.
|
||||||
/// Property filtering and ordering matches runtime TypeMetadataBase exactly:
|
/// Property filtering and ordering matches runtime TypeMetadataBase exactly:
|
||||||
/// public get+set, non-indexer, non-static, no ignore attributes, sorted (Id first if IId, then alphabetical).
|
/// public get+set, non-indexer, non-static, no ignore attributes, sorted alphabetically.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static int[] ComputeChildPropertyHashes(ITypeSymbol resolvedType)
|
private static int[] ComputeChildPropertyHashes(ITypeSymbol resolvedType)
|
||||||
{
|
{
|
||||||
|
|
@ -1087,19 +1115,6 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var childIsIId = resolvedType.AllInterfaces.Any(ifc =>
|
|
||||||
ifc.IsGenericType &&
|
|
||||||
ifc.OriginalDefinition.ToDisplayString() == "AyCode.Core.Interfaces.IId<T>");
|
|
||||||
|
|
||||||
if (childIsIId)
|
|
||||||
propNames.Sort((a, b) =>
|
|
||||||
{
|
|
||||||
var ai = a == "Id" ? 0 : 1;
|
|
||||||
var bi = b == "Id" ? 0 : 1;
|
|
||||||
if (ai != bi) return ai.CompareTo(bi);
|
|
||||||
return string.Compare(a, b, StringComparison.Ordinal);
|
|
||||||
});
|
|
||||||
else
|
|
||||||
propNames.Sort(StringComparer.Ordinal);
|
propNames.Sort(StringComparer.Ordinal);
|
||||||
|
|
||||||
return propNames.Select(ComputeFnvHash).ToArray();
|
return propNames.Select(ComputeFnvHash).ToArray();
|
||||||
|
|
@ -1201,12 +1216,14 @@ internal sealed class SerializableClassInfo
|
||||||
public bool IsIId { get; }
|
public bool IsIId { get; }
|
||||||
/// <summary>The Id type name ("int", "long", "System.Guid") if IsIId, null otherwise</summary>
|
/// <summary>The Id type name ("int", "long", "System.Guid") if IsIId, null otherwise</summary>
|
||||||
public string? IdTypeName { get; }
|
public string? IdTypeName { get; }
|
||||||
|
/// <summary>True if EnableRefHandlingFeature is enabled — controls non-IId All mode tracking code emission.</summary>
|
||||||
|
public bool EnableRefHandling { get; }
|
||||||
/// <summary>FNV-1a hash of ClassName (matches runtime SourceType.Name hash)</summary>
|
/// <summary>FNV-1a hash of ClassName (matches runtime SourceType.Name hash)</summary>
|
||||||
public int TypeNameHash { get; }
|
public int TypeNameHash { get; }
|
||||||
/// <summary>FNV-1a hash of each property name, in property order</summary>
|
/// <summary>FNV-1a hash of each property name, in property order</summary>
|
||||||
public int[] PropertyNameHashes { get; }
|
public int[] PropertyNameHashes { get; }
|
||||||
public SerializableClassInfo(string ns, string cn, string ftn, List<PropInfo> p, bool isIId, string? idTypeName, int typeNameHash, int[] propertyNameHashes)
|
public SerializableClassInfo(string ns, string cn, string ftn, List<PropInfo> p, bool isIId, string? idTypeName, bool enableRefHandling, int typeNameHash, int[] propertyNameHashes)
|
||||||
{ Namespace = ns; ClassName = cn; FullTypeName = ftn; Properties = p; IsIId = isIId; IdTypeName = idTypeName; TypeNameHash = typeNameHash; PropertyNameHashes = propertyNameHashes; }
|
{ Namespace = ns; ClassName = cn; FullTypeName = ftn; Properties = p; IsIId = isIId; IdTypeName = idTypeName; EnableRefHandling = enableRefHandling; TypeNameHash = typeNameHash; PropertyNameHashes = propertyNameHashes; }
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class PropInfo
|
internal sealed class PropInfo
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ public enum TestUserRole
|
||||||
/// Implements IId<int> for semantic $id/$ref serialization.
|
/// Implements IId<int> for semantic $id/$ref serialization.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MemoryPackable]
|
[MemoryPackable]
|
||||||
[AcBinarySerializable]
|
[AcBinarySerializable(false)]
|
||||||
[MessagePackObject]
|
[MessagePackObject]
|
||||||
public partial class SharedTag : IId<int>
|
public partial class SharedTag : IId<int>
|
||||||
{
|
{
|
||||||
|
|
@ -80,7 +80,7 @@ public partial class SharedTag : IId<int>
|
||||||
/// Shared category - for hierarchical cross-reference testing.
|
/// Shared category - for hierarchical cross-reference testing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MemoryPackable]
|
[MemoryPackable]
|
||||||
[AcBinarySerializable]
|
[AcBinarySerializable(false)]
|
||||||
[MessagePackObject]
|
[MessagePackObject]
|
||||||
public partial class SharedCategory : IId<int>
|
public partial class SharedCategory : IId<int>
|
||||||
{
|
{
|
||||||
|
|
@ -106,7 +106,7 @@ public partial class SharedCategory : IId<int>
|
||||||
/// Shared user reference - appears in many places to test $ref deduplication.
|
/// Shared user reference - appears in many places to test $ref deduplication.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MemoryPackable]
|
[MemoryPackable]
|
||||||
[AcBinarySerializable]
|
[AcBinarySerializable(false)]
|
||||||
[MessagePackObject]
|
[MessagePackObject]
|
||||||
public partial class SharedUser : IId<int>
|
public partial class SharedUser : IId<int>
|
||||||
{
|
{
|
||||||
|
|
@ -136,7 +136,7 @@ public partial class SharedUser : IId<int>
|
||||||
/// User preferences - non-IId nested object
|
/// User preferences - non-IId nested object
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MemoryPackable]
|
[MemoryPackable]
|
||||||
[AcBinarySerializable]
|
[AcBinarySerializable(false)]
|
||||||
[MessagePackObject]
|
[MessagePackObject]
|
||||||
public partial class UserPreferences
|
public partial class UserPreferences
|
||||||
{
|
{
|
||||||
|
|
@ -162,7 +162,7 @@ public partial class UserPreferences
|
||||||
/// Does NOT implement IId, so uses standard Newtonsoft reference tracking.
|
/// Does NOT implement IId, so uses standard Newtonsoft reference tracking.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MemoryPackable]
|
[MemoryPackable]
|
||||||
[AcBinarySerializable]
|
[AcBinarySerializable(false)]
|
||||||
[MessagePackObject]
|
[MessagePackObject]
|
||||||
public partial class MetadataInfo
|
public partial class MetadataInfo
|
||||||
{
|
{
|
||||||
|
|
@ -190,7 +190,7 @@ public partial class MetadataInfo
|
||||||
/// Level 1: Main order - root of the hierarchy
|
/// Level 1: Main order - root of the hierarchy
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MemoryPackable]
|
[MemoryPackable]
|
||||||
[AcBinarySerializable]
|
[AcBinarySerializable(false)]
|
||||||
[MessagePackObject]
|
[MessagePackObject]
|
||||||
public partial class TestOrder : IId<int>
|
public partial class TestOrder : IId<int>
|
||||||
{
|
{
|
||||||
|
|
@ -250,7 +250,7 @@ public partial class TestOrder : IId<int>
|
||||||
/// Level 2: Order item with pallets
|
/// Level 2: Order item with pallets
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MemoryPackable]
|
[MemoryPackable]
|
||||||
[AcBinarySerializable]
|
[AcBinarySerializable(false)]
|
||||||
[MessagePackObject]
|
[MessagePackObject]
|
||||||
public partial class TestOrderItem : IId<int>
|
public partial class TestOrderItem : IId<int>
|
||||||
{
|
{
|
||||||
|
|
@ -290,7 +290,7 @@ public partial class TestOrderItem : IId<int>
|
||||||
/// Level 3: Pallet containing measurements
|
/// Level 3: Pallet containing measurements
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MemoryPackable]
|
[MemoryPackable]
|
||||||
[AcBinarySerializable]
|
[AcBinarySerializable(false)]
|
||||||
[MessagePackObject]
|
[MessagePackObject]
|
||||||
public partial class TestPallet : IId<int>
|
public partial class TestPallet : IId<int>
|
||||||
{
|
{
|
||||||
|
|
@ -333,7 +333,7 @@ public partial class TestPallet : IId<int>
|
||||||
/// Level 4: Measurement with multiple points
|
/// Level 4: Measurement with multiple points
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MemoryPackable]
|
[MemoryPackable]
|
||||||
[AcBinarySerializable]
|
[AcBinarySerializable(false)]
|
||||||
[MessagePackObject]
|
[MessagePackObject]
|
||||||
public partial class TestMeasurement : IId<int>
|
public partial class TestMeasurement : IId<int>
|
||||||
{
|
{
|
||||||
|
|
@ -368,7 +368,7 @@ public partial class TestMeasurement : IId<int>
|
||||||
/// Level 5: Deepest level - measurement point
|
/// Level 5: Deepest level - measurement point
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MemoryPackable]
|
[MemoryPackable]
|
||||||
[AcBinarySerializable]
|
[AcBinarySerializable(false)]
|
||||||
[MessagePackObject]
|
[MessagePackObject]
|
||||||
public partial class TestMeasurementPoint : IId<int>
|
public partial class TestMeasurementPoint : IId<int>
|
||||||
{
|
{
|
||||||
|
|
@ -402,7 +402,7 @@ public partial class TestMeasurementPoint : IId<int>
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Order with Guid Id - for testing Guid-based IId
|
/// Order with Guid Id - for testing Guid-based IId
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AcBinarySerializable]
|
[AcBinarySerializable(false)]
|
||||||
public class TestGuidOrder : IId<Guid>
|
public class TestGuidOrder : IId<Guid>
|
||||||
{
|
{
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
|
|
@ -414,7 +414,7 @@ public class TestGuidOrder : IId<Guid>
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Item with Guid Id
|
/// Item with Guid Id
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AcBinarySerializable]
|
[AcBinarySerializable(false)]
|
||||||
public class TestGuidItem : IId<Guid>
|
public class TestGuidItem : IId<Guid>
|
||||||
{
|
{
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
|
|
@ -430,7 +430,7 @@ public class TestGuidItem : IId<Guid>
|
||||||
/// Simulates NopCommerce GenericAttribute - stores key-value pairs where DateTime values
|
/// Simulates NopCommerce GenericAttribute - stores key-value pairs where DateTime values
|
||||||
/// are stored as strings in the database.
|
/// are stored as strings in the database.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AcBinarySerializable]
|
[AcBinarySerializable(false)]
|
||||||
public class TestGenericAttribute
|
public class TestGenericAttribute
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
@ -442,7 +442,7 @@ public class TestGenericAttribute
|
||||||
/// DTO with GenericAttributes collection - simulates OrderDto with string-stored DateTime values.
|
/// DTO with GenericAttributes collection - simulates OrderDto with string-stored DateTime values.
|
||||||
/// This reproduces the production bug where Binary serialization was thought to corrupt DateTime strings.
|
/// This reproduces the production bug where Binary serialization was thought to corrupt DateTime strings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AcBinarySerializable]
|
[AcBinarySerializable(false)]
|
||||||
public class TestDtoWithGenericAttributes : IId<int>
|
public class TestDtoWithGenericAttributes : IId<int>
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
@ -453,7 +453,7 @@ public class TestDtoWithGenericAttributes : IId<int>
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Order with nullable collections for null vs empty testing
|
/// Order with nullable collections for null vs empty testing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AcBinarySerializable]
|
[AcBinarySerializable(false)]
|
||||||
public class TestOrderWithNullableCollections
|
public class TestOrderWithNullableCollections
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
@ -466,7 +466,7 @@ public class TestOrderWithNullableCollections
|
||||||
/// Class with all primitive types for WASM/serialization testing
|
/// Class with all primitive types for WASM/serialization testing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MemoryPackable]
|
[MemoryPackable]
|
||||||
[AcBinarySerializable]
|
[AcBinarySerializable(false)]
|
||||||
public partial class PrimitiveTestClass
|
public partial class PrimitiveTestClass
|
||||||
{
|
{
|
||||||
public int IntValue { get; set; }
|
public int IntValue { get; set; }
|
||||||
|
|
@ -489,7 +489,7 @@ public partial class PrimitiveTestClass
|
||||||
/// Class with extended primitive types for full serializer coverage.
|
/// Class with extended primitive types for full serializer coverage.
|
||||||
/// Includes DateTimeOffset, TimeSpan, Dictionary, null properties.
|
/// Includes DateTimeOffset, TimeSpan, Dictionary, null properties.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AcBinarySerializable]
|
[AcBinarySerializable(false)]
|
||||||
public class ExtendedPrimitiveTestClass
|
public class ExtendedPrimitiveTestClass
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
@ -519,7 +519,7 @@ public class ExtendedPrimitiveTestClass
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class with array of objects containing null items for WriteNull coverage
|
/// Class with array of objects containing null items for WriteNull coverage
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AcBinarySerializable]
|
[AcBinarySerializable(false)]
|
||||||
public class ObjectWithNullItems
|
public class ObjectWithNullItems
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
@ -534,7 +534,7 @@ public class ObjectWithNullItems
|
||||||
/// "Server-side" DTO with extra properties that the "client" doesn't know about.
|
/// "Server-side" DTO with extra properties that the "client" doesn't know about.
|
||||||
/// Used to test SkipValue functionality when deserializing unknown properties.
|
/// Used to test SkipValue functionality when deserializing unknown properties.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AcBinarySerializable]
|
[AcBinarySerializable(false)]
|
||||||
public class ServerCustomerDto : IId<int>
|
public class ServerCustomerDto : IId<int>
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
@ -567,7 +567,7 @@ public class ServerCustomerDto : IId<int>
|
||||||
/// the deserializer must skip unknown properties correctly
|
/// the deserializer must skip unknown properties correctly
|
||||||
/// while still maintaining string intern table consistency.
|
/// while still maintaining string intern table consistency.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AcBinarySerializable]
|
[AcBinarySerializable(false)]
|
||||||
public class ClientCustomerDto : IId<int>
|
public class ClientCustomerDto : IId<int>
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
@ -581,7 +581,7 @@ public class ClientCustomerDto : IId<int>
|
||||||
/// Server DTO with nested objects that client doesn't know about.
|
/// Server DTO with nested objects that client doesn't know about.
|
||||||
/// Tests skipping complex nested structures.
|
/// Tests skipping complex nested structures.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AcBinarySerializable]
|
[AcBinarySerializable(false)]
|
||||||
public class ServerOrderWithExtras : IId<int>
|
public class ServerOrderWithExtras : IId<int>
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
@ -602,7 +602,7 @@ public class ServerOrderWithExtras : IId<int>
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Client version of the order - doesn't have Customer/RelatedCustomers properties.
|
/// Client version of the order - doesn't have Customer/RelatedCustomers properties.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AcBinarySerializable]
|
[AcBinarySerializable(false)]
|
||||||
public class ClientOrderSimple : IId<int>
|
public class ClientOrderSimple : IId<int>
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ public abstract class AcSerializerOptions
|
||||||
set => _referenceHandling = value;
|
set => _referenceHandling = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ReferenceHandlingMode _referenceHandling = ReferenceHandlingMode.OnlyId;
|
private ReferenceHandlingMode _referenceHandling = ReferenceHandlingMode.All;
|
||||||
|
|
||||||
private readonly byte _maxDepth = byte.MaxValue;
|
private readonly byte _maxDepth = byte.MaxValue;
|
||||||
private readonly bool _throwOnCircularReference = true;
|
private readonly bool _throwOnCircularReference = true;
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,8 @@ public static partial class AcBinarySerializer
|
||||||
for (var i = 0; i < Properties.Length; i++)
|
for (var i = 0; i < Properties.Length; i++)
|
||||||
{
|
{
|
||||||
var prop = Properties[i];
|
var prop = Properties[i];
|
||||||
if (prop.IsComplexType || (EnableInternString && prop.AccessorType == PropertyAccessorType.String))
|
if ((prop.IsComplexType && !(prop.IsStringCollectionProperty && !EnableInternString)) ||
|
||||||
|
(EnableInternString && prop.AccessorType == PropertyAccessorType.String))
|
||||||
list.Add(prop);
|
list.Add(prop);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,6 +126,7 @@ public static partial class AcBinarySerializer
|
||||||
// Read [AcBinarySerializable] once per type — passed to property accessors
|
// Read [AcBinarySerializable] once per type — passed to property accessors
|
||||||
var serializableAttr = type.GetCustomAttribute<AcBinarySerializableAttribute>(inherit: false);
|
var serializableAttr = type.GetCustomAttribute<AcBinarySerializableAttribute>(inherit: false);
|
||||||
EnableInternString = serializableAttr == null || serializableAttr.EnableInternStringFeature;
|
EnableInternString = serializableAttr == null || serializableAttr.EnableInternStringFeature;
|
||||||
|
var enableRefHandling = serializableAttr == null || serializableAttr.EnableRefHandlingFeature;
|
||||||
|
|
||||||
Properties = new BinaryPropertyAccessor[orderedProperties.Length];
|
Properties = new BinaryPropertyAccessor[orderedProperties.Length];
|
||||||
var complexCount = 0;
|
var complexCount = 0;
|
||||||
|
|
@ -147,10 +149,9 @@ public static partial class AcBinarySerializer
|
||||||
HasComplexProperties = complexCount > 0;
|
HasComplexProperties = complexCount > 0;
|
||||||
|
|
||||||
// Type needs reference tracking if:
|
// Type needs reference tracking if:
|
||||||
// 1. It's IId (can be deduplicated by Id)
|
// 1. It's IId (can be deduplicated by Id) — already false if EnableIdTrackingFeature == false
|
||||||
// 2. It has complex properties (children could be shared)
|
// 2. EnableRefHandlingFeature enabled AND (has complex properties or non-primitive)
|
||||||
// 3. It's not a primitive/string (could be referenced multiple times)
|
NeedsReferenceTracking = IsIId || (enableRefHandling && (HasComplexProperties || !IsPrimitiveType));
|
||||||
NeedsReferenceTracking = IsIId || HasComplexProperties || !IsPrimitiveType;
|
|
||||||
|
|
||||||
// Fast check: only look for generated serializer if type has [AcBinarySerializable] attribute
|
// Fast check: only look for generated serializer if type has [AcBinarySerializable] attribute
|
||||||
if (false && type.IsDefined(typeof(AcBinarySerializableAttribute), inherit: false))
|
if (false && type.IsDefined(typeof(AcBinarySerializableAttribute), inherit: false))
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ public sealed class AcBinarySerializerOptions : AcSerializerOptions
|
||||||
/// allowing the deserializer to match properties by name between different types.
|
/// allowing the deserializer to match properties by name between different types.
|
||||||
/// Default: false (no overhead)
|
/// Default: false (no overhead)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool UseMetadata { get; set; } = true;
|
public bool UseMetadata { get; set; } = false;
|
||||||
|
|
||||||
public bool UseGeneratedCode { get; set; } = true;
|
public bool UseGeneratedCode { get; set; } = true;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using AyCode.Core.Helpers;
|
using AyCode.Core.Helpers;
|
||||||
using AyCode.Core.Interfaces;
|
using AyCode.Core.Interfaces;
|
||||||
|
using AyCode.Core.Serializers.Attributes;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
|
@ -185,6 +186,7 @@ public abstract class TypeMetadataBase
|
||||||
{
|
{
|
||||||
SourceType = type;
|
SourceType = type;
|
||||||
_ignorePropertyFilter = ignorePropertyFilter;
|
_ignorePropertyFilter = ignorePropertyFilter;
|
||||||
|
|
||||||
CompiledConstructor = AcSerializerCommon.CreateCompiledConstructor(type);
|
CompiledConstructor = AcSerializerCommon.CreateCompiledConstructor(type);
|
||||||
|
|
||||||
// Pre-compute property arrays - no dictionary lookup needed later!
|
// Pre-compute property arrays - no dictionary lookup needed later!
|
||||||
|
|
@ -192,6 +194,7 @@ public abstract class TypeMetadataBase
|
||||||
var allReadable = GetUnfilteredProperties(type, requiresWrite: false)
|
var allReadable = GetUnfilteredProperties(type, requiresWrite: false)
|
||||||
.Where(p => !ignorePropertyFilter(p))
|
.Where(p => !ignorePropertyFilter(p))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
ReadableProperties = allReadable;
|
ReadableProperties = allReadable;
|
||||||
WritableProperties = allReadable.Where(p => p.CanWrite).ToArray();
|
WritableProperties = allReadable.Where(p => p.CanWrite).ToArray();
|
||||||
|
|
||||||
|
|
@ -211,11 +214,18 @@ public abstract class TypeMetadataBase
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var serializableAttr = type.GetCustomAttribute<AcBinarySerializableAttribute>(inherit: false);
|
||||||
|
if (serializableAttr is { EnableIdTrackingFeature: false })
|
||||||
|
{
|
||||||
|
IsIId = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
// 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)
|
||||||
{
|
{
|
||||||
|
|
@ -279,7 +289,7 @@ public abstract class TypeMetadataBase
|
||||||
return requiresWrite ? WritableProperties : ReadableProperties;
|
return requiresWrite ? WritableProperties : ReadableProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Id properties are always at index 0 (sorted first) in UnfilteredPropertiesGlobalCache!
|
// Id properties are sorted alphabetically like all other properties
|
||||||
public const string IdPropertyName = nameof(IId<int>.Id);
|
public const string IdPropertyName = nameof(IId<int>.Id);
|
||||||
|
|
||||||
private static List<PropertyInfo> GetUnfilteredProperties(Type type, bool requiresWrite)
|
private static List<PropertyInfo> GetUnfilteredProperties(Type type, bool requiresWrite)
|
||||||
|
|
@ -288,11 +298,8 @@ public abstract class TypeMetadataBase
|
||||||
{
|
{
|
||||||
var (t, needsWrite) = key;
|
var (t, needsWrite) = key;
|
||||||
|
|
||||||
// Check if type implements IId - if so, Id property will be first
|
|
||||||
var isIId = GetIdInfo(t).IsId;
|
|
||||||
|
|
||||||
// Collect properties from inheritance hierarchy (derived -> base order)
|
// Collect properties from inheritance hierarchy (derived -> base order)
|
||||||
// Sort: IId types have Id first, then alphabetical
|
// Sorted alphabetically for deterministic property index ordering
|
||||||
var allProperties = new List<PropertyInfo>();
|
var allProperties = new List<PropertyInfo>();
|
||||||
|
|
||||||
for (var currentType = t; currentType != null && currentType != typeof(object); currentType = currentType.BaseType)
|
for (var currentType = t; currentType != null && currentType != typeof(object); currentType = currentType.BaseType)
|
||||||
|
|
@ -304,10 +311,7 @@ public abstract class TypeMetadataBase
|
||||||
(!needsWrite || p.CanWrite) &&
|
(!needsWrite || p.CanWrite) &&
|
||||||
p.GetIndexParameters().Length == 0 &&
|
p.GetIndexParameters().Length == 0 &&
|
||||||
!IsUnsupportedPropertyType(p.PropertyType))
|
!IsUnsupportedPropertyType(p.PropertyType))
|
||||||
// IId: Id first (0), then alphabetical (1)
|
.OrderBy(static p => p.Name, StringComparer.Ordinal);
|
||||||
// Non-IId: all alphabetical
|
|
||||||
.OrderBy(p => isIId && p.Name == IdPropertyName ? 0 : 1)
|
|
||||||
.ThenBy(static p => p.Name, StringComparer.Ordinal);
|
|
||||||
|
|
||||||
allProperties.AddRange(levelProperties);
|
allProperties.AddRange(levelProperties);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue