Enable property-level string interning via attribute

Introduce [AcStringIntern] for selective string interning on properties. Update serializer to use a per-property eligibility flag, improving efficiency and control over which string fields participate in interning during binary serialization. Update test models and internal context accordingly.
This commit is contained in:
Loretta 2026-02-15 19:01:21 +01:00
parent 6f88306e54
commit e30efff56c
4 changed files with 26 additions and 5 deletions

View File

@ -1,4 +1,5 @@
using AyCode.Core.Interfaces;
using AyCode.Core.Serializers.Binaries;
namespace AyCode.Core.Tests.TestModels;
@ -30,7 +31,9 @@ public static class AcSerializerModels
public class TestClassWithRepeatedStrings
{
[AcStringIntern(true)]
public string Field1 { get; set; } = "";
[AcStringIntern(true)]
public string Field2 { get; set; } = "";
public string Field3 { get; set; } = "";
public string Field4 { get; set; } = "";

View File

@ -1,6 +1,7 @@
using AyCode.Core.Extensions;
using AyCode.Core.Interfaces;
using AyCode.Core.Serializers.Attributes;
using AyCode.Core.Serializers.Binaries;
using AyCode.Core.Serializers.Jsons;
using MessagePack;
using MongoDB.Bson.Serialization.Attributes;
@ -60,6 +61,7 @@ public class SharedTag : IId<int>
public int Id { get; set; }
[Key(1)]
public string Name { get; set; } = "";
[AcStringIntern(true)]
[Key(2)]
public string Color { get; set; } = "#000000";
[Key(3)]
@ -133,12 +135,15 @@ public class SharedUser : IId<int>
[MessagePackObject]
public class UserPreferences
{
[AcStringIntern(true)]
[Key(0)]
public string Theme { get; set; } = "light";
[AcStringIntern(true)]
[Key(1)]
public string Language { get; set; } = "en-US";
[Key(2)]
public bool NotificationsEnabled { get; set; } = true;
[AcStringIntern(true)]
[Key(3)]
public string? EmailDigestFrequency { get; set; }
}
@ -155,8 +160,10 @@ public class UserPreferences
[MessagePackObject]
public class MetadataInfo
{
[AcStringIntern(true)]
[Key(0)]
public string Key { get; set; } = "";
[AcStringIntern(true)]
[Key(1)]
public string Value { get; set; } = "";
[Key(2)]
@ -240,6 +247,7 @@ public class TestOrderItem : IId<int>
{
[Key(0)]
public int Id { get; set; }
[AcStringIntern(true)]
[Key(1)]
public string ProductName { get; set; } = "";
[Key(2)]

View File

@ -146,6 +146,13 @@ public static partial class AcBinarySerializer
/// <summary>Write pass visit counter. Mirrors ScanVisitIndex ordering.</summary>
internal int WriteVisitIndex;
/// <summary>
/// Set per-property in WritePropertyOrSkip before calling WriteString.
/// Controls whether the current string property participates in the cursor-based interning.
/// Must mirror scan pass's prop.UseStringPropertyInterning() check.
/// </summary>
internal bool StringInternEligible;
/// <summary>
/// Tries to consume the next write plan entry at the current WriteVisitIndex.
/// Returns true if the entry matches (duplicate exists at this visit point).
@ -264,6 +271,7 @@ public static partial class AcBinarySerializer
ScanVisitIndex = 0;
WritePlanCursor = 0;
WriteVisitIndex = 0;
StringInternEligible = false;
// Clear write plan string references to avoid GC pinning, keep array if small enough
if (_writePlan != null)

View File

@ -912,8 +912,10 @@ public static partial class AcBinarySerializer
return;
}
if (context.UseStringInterning && context.IsValidForInterningString(value.Length))
if (context.StringInternEligible && context.IsValidForInterningString(value.Length))
{
context.StringInternEligible = false;
if (context.TryConsumeWritePlanEntry(out var planEntry))
{
ValidateWritePlanString(in planEntry, value);
@ -1383,6 +1385,7 @@ public static partial class AcBinarySerializer
}
else
{
context.StringInternEligible = prop.UseStringPropertyInterning(context.Options.UseStringInterning);
WriteString(value, context);
}
return;
@ -1391,6 +1394,8 @@ public static partial class AcBinarySerializer
{
// Object type (collection, complex object, byte[], dictionary)
// Use pre-cached wrapper, fallback to GetWrapper on miss/polymorphism
// Set interning eligibility for string collection elements
context.StringInternEligible = prop.IsStringCollectionProperty && prop.UseStringPropertyInterning(context.Options.UseStringInterning);
var value = prop.GetValue(obj);
// SKIP marker only for null (reference types)
@ -1736,10 +1741,7 @@ public static partial class AcBinarySerializer
/// Uses the typed Id getter to extract the Id and compares against the scan pass IdentityMap.
/// </summary>
[Conditional("DEBUG")]
private static void ValidateWritePlanObject(
in WriteDuplicateEntry planEntry,
object value,
TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper)
private static void ValidateWritePlanObject(in WriteDuplicateEntry planEntry, object value, TypeMetadataWrapper<BinarySerializeTypeMetadata> wrapper)
{
var metadata = wrapper.Metadata;
switch (metadata.IdAccessorType)