diff --git a/AyCode.Benchmark/SerializationBenchmarks.cs b/AyCode.Benchmark/SerializationBenchmarks.cs
index 471eecf..7fed174 100644
--- a/AyCode.Benchmark/SerializationBenchmarks.cs
+++ b/AyCode.Benchmark/SerializationBenchmarks.cs
@@ -535,7 +535,7 @@ public abstract class AcBinaryOptionsBenchmarkBase
BinaryBenchmarkMode.FastMode => new AcBinarySerializerOptions
{
UseMetadata = false,
- UseStringInterning = false,
+ UseStringInterning = StringInterningMode.None,
ReferenceHandling = ReferenceHandlingMode.None,
},
_ => new AcBinarySerializerOptions()
diff --git a/AyCode.Core.Serializers.Console/Program.cs b/AyCode.Core.Serializers.Console/Program.cs
index c9b461f..246fb91 100644
--- a/AyCode.Core.Serializers.Console/Program.cs
+++ b/AyCode.Core.Serializers.Console/Program.cs
@@ -121,7 +121,7 @@ public static class Program
sharedUser: sharedUser);
var options = AcBinarySerializerOptions.WithoutReferenceHandling;
- options.UseStringInterning = false;
+ options.UseStringInterning = StringInterningMode.None;
// Warmup (fills caches)
System.Console.WriteLine("Warming up (10 iterations)...");
@@ -419,7 +419,7 @@ public static class Program
new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.Default, SerializerAcBinaryDefault),
new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.WithoutReferenceHandling, SerializerAcBinaryNoRef),
new AcBinaryBenchmark(testData.Order, AcBinarySerializerOptions.FastMode, SerializerAcBinaryFastMode),
- new AcBinaryBenchmark(testData.Order, new AcBinarySerializerOptions { UseStringInterning = false }, SerializerAcBinaryNoIntern),
+ new AcBinaryBenchmark(testData.Order, new AcBinarySerializerOptions { UseStringInterning = StringInterningMode.None }, SerializerAcBinaryNoIntern),
// AcJson
new AcJsonBenchmark(testData.Order, AcJsonSerializerOptions.Default, SerializerAcJsonDefault),
diff --git a/AyCode.Core.Tests/Serialization/AcBinarySerializerBenchmarkTests.cs b/AyCode.Core.Tests/Serialization/AcBinarySerializerBenchmarkTests.cs
index 7c0f194..03e538d 100644
--- a/AyCode.Core.Tests/Serialization/AcBinarySerializerBenchmarkTests.cs
+++ b/AyCode.Core.Tests/Serialization/AcBinarySerializerBenchmarkTests.cs
@@ -62,7 +62,7 @@ public class AcBinarySerializerBenchmarkTests
var order = TestDataFactory.CreateBenchmarkOrder(itemCount: 5, palletsPerItem: 3, measurementsPerPallet: 2, pointsPerMeasurement: 5);
var binaryWithInterning = AcBinarySerializer.Serialize(order, AcBinarySerializerOptions.Default);
- var binaryWithoutInterning = AcBinarySerializer.Serialize(order, new AcBinarySerializerOptions { UseStringInterning = false });
+ var binaryWithoutInterning = AcBinarySerializer.Serialize(order, new AcBinarySerializerOptions { UseStringInterning = StringInterningMode.None });
// Note: String interning may not always result in smaller size due to header overhead
// The primary benefit is for larger datasets with many repeated strings
diff --git a/AyCode.Core.Tests/Serialization/AcBinarySerializerStringInterningTests.cs b/AyCode.Core.Tests/Serialization/AcBinarySerializerStringInterningTests.cs
index d71d16b..97a37d0 100644
--- a/AyCode.Core.Tests/Serialization/AcBinarySerializerStringInterningTests.cs
+++ b/AyCode.Core.Tests/Serialization/AcBinarySerializerStringInterningTests.cs
@@ -24,7 +24,7 @@ public class AcBinarySerializerStringInterningTests
var binaryWithInterning = AcBinarySerializer.Serialize(obj, AcBinarySerializerOptions.Default);
var binaryWithoutInterning = AcBinarySerializer.Serialize(obj,
- new AcBinarySerializerOptions { UseStringInterning = false });
+ new AcBinarySerializerOptions { UseStringInterning = StringInterningMode.None });
Assert.IsTrue(binaryWithInterning.Length < binaryWithoutInterning.Length,
$"With interning: {binaryWithInterning.Length}, Without: {binaryWithoutInterning.Length}");
diff --git a/AyCode.Core.Tests/Serialization/QuickBenchmark.cs b/AyCode.Core.Tests/Serialization/QuickBenchmark.cs
index 97c9ec1..2d9b9a6 100644
--- a/AyCode.Core.Tests/Serialization/QuickBenchmark.cs
+++ b/AyCode.Core.Tests/Serialization/QuickBenchmark.cs
@@ -197,7 +197,7 @@ public class QuickBenchmark
var withInterningMs = sw.Elapsed.TotalMilliseconds;
// Without interning
- var noInternOptions = new AcBinarySerializerOptions { UseStringInterning = false };
+ var noInternOptions = new AcBinarySerializerOptions { UseStringInterning = StringInterningMode.None };
sw.Restart();
byte[] withoutInterning = null!;
for (int i = 0; i < iterations; i++)
@@ -427,7 +427,7 @@ public class QuickBenchmark
sharedMetadata: sharedMeta);
var singleOptions = AcBinarySerializerOptions.FastMode;
- singleOptions.UseStringInterning = false;
+ singleOptions.UseStringInterning = StringInterningMode.None;
Console.WriteLine("=== MINIMAL WARMUP TEST ===");
Console.WriteLine();
@@ -507,9 +507,9 @@ public class QuickBenchmark
// Options
var withRefOptions = AcBinarySerializerOptions.Default;
- //withRefOptions.UseStringInterning = false;
+ //withRefOptions.UseStringInterning = StringInterningMode.None;
var noRefOptions = AcBinarySerializerOptions.WithoutReferenceHandling;
- noRefOptions.UseStringInterning = false;
+ noRefOptions.UseStringInterning = StringInterningMode.None;
// Pre-serialize
var acBinaryWithRef = AcBinarySerializer.Serialize(testOrder, withRefOptions);
diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs
index 7ac0d3c..1628650 100644
--- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs
+++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs
@@ -92,7 +92,7 @@ public static partial class AcBinarySerializer
#endif
// These properties delegate to Options for convenience
- public bool UseStringInterning => Options.UseStringInterning;
+ public bool UseStringInterning => Options.UseStringInterning != StringInterningMode.None;
public bool UseMetadata => Options.UseMetadata;
public byte MinStringInternLength => Options.MinStringInternLength;
public byte MaxStringInternLength => Options.MaxStringInternLength;
diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs
index ec5d747..6de9d2a 100644
--- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs
+++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs
@@ -60,9 +60,9 @@ public static partial class AcBinarySerializer
options ??= AcBinarySerializerOptions.Default;
// For analysis, use the provided reference handling mode
- var analysisOptions = new AcBinarySerializerOptions
- {
- UseStringInterning = true,
+ var analysisOptions = new AcBinarySerializerOptions
+ {
+ UseStringInterning = StringInterningMode.All,
MinStringInternLength = options.MinStringInternLength,
MaxStringInternLength = options.MaxStringInternLength,
ReferenceHandling = options.ReferenceHandling
diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs
index 767b510..4edf5c1 100644
--- a/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs
+++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs
@@ -33,7 +33,7 @@ public sealed class AcBinarySerializerOptions : AcSerializerOptions
///
public static AcBinarySerializerOptions FastMode => new()
{
- UseStringInterning = false,
+ UseStringInterning = StringInterningMode.None,
ReferenceHandling = ReferenceHandlingMode.None
};
@@ -44,7 +44,7 @@ public sealed class AcBinarySerializerOptions : AcSerializerOptions
public static AcBinarySerializerOptions ShallowCopy => new()
{
MaxDepth = 0,
- UseStringInterning = false,
+ UseStringInterning = StringInterningMode.None,
ReferenceHandling = ReferenceHandlingMode.None
};
@@ -89,12 +89,13 @@ public sealed class AcBinarySerializerOptions : AcSerializerOptions
public bool UseMetadata { get; init; } = false;
///
- /// Whether to intern repeated strings.
- /// When enabled, duplicate strings are stored once and referenced by index.
- /// Reduces size and memory for objects with many repeated string values.
- /// Default: true
+ /// Controls how string interning is applied during serialization.
+ /// None: No interning, all strings written inline.
+ /// Attribute: Only properties with [AcStringIntern(true)] are interned.
+ /// All: All strings within length limits are interned (legacy behavior).
+ /// Default: All
///
- public bool UseStringInterning { get; set; } = true;
+ public StringInterningMode UseStringInterning { get; set; } = StringInterningMode.All;
///
/// Minimum string length to consider for interning.
diff --git a/AyCode.Core/Serializers/Binaries/AcStringInternAttribute.cs b/AyCode.Core/Serializers/Binaries/AcStringInternAttribute.cs
new file mode 100644
index 0000000..5d30d37
--- /dev/null
+++ b/AyCode.Core/Serializers/Binaries/AcStringInternAttribute.cs
@@ -0,0 +1,25 @@
+namespace AyCode.Core.Serializers.Binaries;
+
+///
+/// Controls string interning for a specific property during binary serialization.
+/// When StringInterningMode is Attribute: only properties with [AcStringIntern(true)] are interned.
+/// When StringInterningMode is All: properties with [AcStringIntern(false)] opt-out from interning.
+/// Attribute is inherited from base class properties if not explicitly specified.
+///
+[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
+public sealed class AcStringInternAttribute : Attribute
+{
+ ///
+ /// Whether string interning is enabled for this property.
+ ///
+ public bool Enabled { get; }
+
+ ///
+ /// Creates a new AcStringInternAttribute.
+ ///
+ /// True to enable interning, false to disable.
+ public AcStringInternAttribute(bool enabled)
+ {
+ Enabled = enabled;
+ }
+}
diff --git a/AyCode.Core/Serializers/Binaries/StringInterningMode.cs b/AyCode.Core/Serializers/Binaries/StringInterningMode.cs
new file mode 100644
index 0000000..f69e872
--- /dev/null
+++ b/AyCode.Core/Serializers/Binaries/StringInterningMode.cs
@@ -0,0 +1,24 @@
+namespace AyCode.Core.Serializers.Binaries;
+
+///
+/// Controls string interning behavior during binary serialization.
+///
+public enum StringInterningMode
+{
+ ///
+ /// No string interning. All strings are written inline.
+ ///
+ None = 0,
+
+ ///
+ /// Only intern strings on properties marked with [AcStringIntern(true)].
+ /// Properties without attribute or with [AcStringIntern(false)] are written inline.
+ ///
+ Attribute = 1,
+
+ ///
+ /// Intern all strings that meet the length criteria (MinStringInternLength to MaxStringInternLength).
+ /// [AcStringIntern(false)] can opt-out specific properties.
+ ///
+ All = 2
+}