From 852ab53af37677184d2a8d2a35d6dd65a56a2adb Mon Sep 17 00:00:00 2001 From: Loretta Date: Fri, 23 Jan 2026 11:21:42 +0100 Subject: [PATCH] Refactor serializer contexts to use generic options type Refactored AcSerializerContextBase and all derived context classes to accept a generic TOptions parameter, ensuring type-safe access to serializer/deserializer options. Updated Reset methods and option-dependent properties to use the strongly-typed Options property. Added helper methods for reference handling checks and performed minor code cleanups for consistency. This improves type safety, reduces runtime errors, and clarifies context usage across serialization formats. --- .../Serializers/AcSerializerContextBase.cs | 28 ++++++++++++++++--- ...lizer.BinaryDeserializationContextClass.cs | 8 +++++- ...rySerializer.BinarySerializationContext.cs | 20 ++++++------- .../Serializers/DeserializationContextBase.cs | 6 ++-- ...Deserializer.JsonDeserializationContext.cs | 10 +++---- ...JsonSerializer.JsonSerializationContext.cs | 6 ++-- .../Serializers/SerializationContextBase.cs | 6 ++-- ...ToonSerializer.ToonSerializationContext.cs | 9 ++---- AyCode.Core/Serializers/TypeMetadataBase.cs | 9 +++++- .../Serializers/TypeMetadataWrapper.cs | 1 + 10 files changed, 67 insertions(+), 36 deletions(-) diff --git a/AyCode.Core/Serializers/AcSerializerContextBase.cs b/AyCode.Core/Serializers/AcSerializerContextBase.cs index 7117dee..e60fddc 100644 --- a/AyCode.Core/Serializers/AcSerializerContextBase.cs +++ b/AyCode.Core/Serializers/AcSerializerContextBase.cs @@ -13,8 +13,16 @@ namespace AyCode.Core.Serializers; /// GlobalMetadataCache stores metadata (thread-safe), wrappers store per-context tracking. /// /// The concrete metadata type. -public abstract class AcSerializerContextBase where TMetadata : TypeMetadataBase +/// The concrete options type. +public abstract class AcSerializerContextBase + where TMetadata : TypeMetadataBase + where TOptions : AcSerializerOptions { + /// + /// The options used for this context. Set during Reset. + /// + public TOptions Options { get; private set; } = null!; + public byte MaxDepth { get; private set; } public ReferenceHandlingMode ReferenceHandling { get; internal set; } /// @@ -33,6 +41,19 @@ public abstract class AcSerializerContextBase where TMetadata : TypeM /// protected abstract Func MetadataFactory { get; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool UseTypeReferenceHandling(Type type) + { + var wrapper = GetWrapper(type); + return UseTypeReferenceHandling(wrapper.Metadata); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool UseTypeReferenceHandling(TMetadata metaData) + { + return ReferenceHandling != ReferenceHandlingMode.None && (metaData.IsIId || ReferenceHandling == ReferenceHandlingMode.All); + } + #region Wrapper Access /// @@ -119,15 +140,14 @@ public abstract class AcSerializerContextBase where TMetadata : TypeM /// Resets all wrapper tracking states for reuse. /// Does not remove wrappers - keeps them for next operation. /// - public virtual void Reset(in AcSerializerOptions? options) + public virtual void Reset(TOptions options) { foreach (var wrapper in _wrappers.Values) { wrapper.ResetTracking(); } - if (options == null) return; - + Options = options; MaxDepth = options.MaxDepth; ReferenceHandling = options.ReferenceHandling; } diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContextClass.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContextClass.cs index 0798333..acf654d 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContextClass.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContextClass.cs @@ -11,7 +11,7 @@ public static partial class AcBinaryDeserializer /// Inherits from AcSerializerContextBase for unified metadata caching and IId-based reference tracking. /// Used in composition with the ref struct BinaryDeserializationContext. /// - internal sealed class BinaryDeserializationContextClass : AcSerializerContextBase + internal sealed class BinaryDeserializationContextClass : AcSerializerContextBase { /// /// Factory for creating BinaryDeserializeTypeMetadata instances. @@ -19,6 +19,12 @@ public static partial class AcBinaryDeserializer protected override Func MetadataFactory => static t => new BinaryDeserializeTypeMetadata(t); + public BinaryDeserializationContextClass() + { + // Initialize with default options - will be reset with actual options before use + Reset(AcBinarySerializerOptions.Default); + } + /// /// After PopulateObject, checks if we should reuse an existing IId object. /// Uses the unified tracking from AcSerializerContextBase (IdentityMap). diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs index 04e34f3..5171450 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs @@ -49,7 +49,7 @@ public static partial class AcBinarySerializer /// /// Binary serialization context. Public for generated serializers. /// - internal sealed class BinarySerializationContext : SerializationContextBase, IDisposable + internal sealed class BinarySerializationContext : SerializationContextBase, IDisposable { private const int MinBufferSize = 256; private const int PropertyIndexBufferMaxCache = 512; @@ -72,11 +72,12 @@ public static partial class AcBinarySerializer private int[]? _propertyIndexBuffer; private byte[]? _propertyStateBuffer; - public bool UseStringInterning { get; private set; } - public bool UseMetadata { get; private set; } - public byte MinStringInternLength { get; private set; } - public byte MaxStringInternLength { get; private set; } - public BinaryPropertyFilter? PropertyFilter { get; private set; } + // These properties delegate to Options for convenience + public bool UseStringInterning => Options.UseStringInterning; + public bool UseMetadata => Options.UseMetadata; + public byte MinStringInternLength => Options.MinStringInternLength; + public byte MaxStringInternLength => Options.MaxStringInternLength; + public BinaryPropertyFilter? PropertyFilter => Options.PropertyFilter; public int Position => _position; @@ -93,17 +94,12 @@ public static partial class AcBinarySerializer protected override Func MetadataFactory => static t => new BinarySerializeTypeMetadata(t, HasJsonIgnoreAttribute); - public void Reset(AcBinarySerializerOptions options) + public override void Reset(AcBinarySerializerOptions options) { // Reset wrapper tracking state from base class (IId tracking) base.Reset(options); _position = 0; - UseStringInterning = options.UseStringInterning; - UseMetadata = options.UseMetadata; - MinStringInternLength = options.MinStringInternLength; - MaxStringInternLength = options.MaxStringInternLength; - PropertyFilter = options.PropertyFilter; _initialBufferSize = Math.Max(options.InitialBufferCapacity, MinBufferSize); if (_buffer.Length < _initialBufferSize) diff --git a/AyCode.Core/Serializers/DeserializationContextBase.cs b/AyCode.Core/Serializers/DeserializationContextBase.cs index 4f37ceb..67b3440 100644 --- a/AyCode.Core/Serializers/DeserializationContextBase.cs +++ b/AyCode.Core/Serializers/DeserializationContextBase.cs @@ -9,8 +9,10 @@ namespace AyCode.Core.Serializers; /// Derived classes are sealed for JIT devirtualization (direct call speed). /// /// The concrete metadata type for deserialization. -public abstract class DeserializationContextBase : AcSerializerContextBase +/// The concrete options type. +public abstract class DeserializationContextBase : AcSerializerContextBase where TMetadata : TypeMetadataBase + where TOptions : AcSerializerOptions { #region Object Lookup by Id (to be implemented by derived sealed classes) @@ -41,7 +43,7 @@ public abstract class DeserializationContextBase : AcSerializerContex /// /// Resets deserialization-specific state. Called by derived classes. /// - public override void Reset(in AcSerializerOptions options) + public override void Reset(TOptions options) { base.Reset(options); // Future: Reset deserialization-specific state diff --git a/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.JsonDeserializationContext.cs b/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.JsonDeserializationContext.cs index 3c38f6c..b15339e 100644 --- a/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.JsonDeserializationContext.cs +++ b/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.JsonDeserializationContext.cs @@ -22,11 +22,11 @@ public static partial class AcJsonDeserializer } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Return(DeserializationContext context, in AcSerializerOptions options) + public static void Return(DeserializationContext context, in AcJsonSerializerOptions options) { if (Pool.Count < MaxPoolSize) { - context.Clear(null); + context.Clear(options); Pool.Enqueue(context); } } @@ -45,7 +45,7 @@ public static partial class AcJsonDeserializer public readonly int RefId = refId; } - private sealed class DeserializationContext : AcSerializerContextBase + private sealed class DeserializationContext : AcSerializerContextBase { // Use shared reference tracker from AcSerializerCommon private readonly AcSerializerCommon.DeserializationReferenceTracker _refTracker = new(); @@ -75,7 +75,7 @@ public static partial class AcJsonDeserializer Reset(options); } - public override void Reset(in AcSerializerOptions? options) + public override void Reset(AcJsonSerializerOptions options) { IsMergeMode = false; ChainTracker = null; @@ -84,7 +84,7 @@ public static partial class AcJsonDeserializer base.Reset(options); } - public void Clear(in AcSerializerOptions? options) + public void Clear(AcJsonSerializerOptions options) { _refTracker.Reset(); _propertiesToResolve?.Clear(); diff --git a/AyCode.Core/Serializers/Jsons/AcJsonSerializer.JsonSerializationContext.cs b/AyCode.Core/Serializers/Jsons/AcJsonSerializer.JsonSerializationContext.cs index 3f2bbfb..3374d60 100644 --- a/AyCode.Core/Serializers/Jsons/AcJsonSerializer.JsonSerializationContext.cs +++ b/AyCode.Core/Serializers/Jsons/AcJsonSerializer.JsonSerializationContext.cs @@ -35,7 +35,7 @@ public static partial class AcJsonSerializer } } - private sealed class JsonSerializationContext : SerializationContextBase, IDisposable + private sealed class JsonSerializationContext : SerializationContextBase, IDisposable { private readonly ArrayBufferWriter _buffer; public Utf8JsonWriter Writer { get; private set; } @@ -62,11 +62,11 @@ public static partial class AcJsonSerializer protected override Func MetadataFactory => static t => new JsonSerializeTypeMetadata(t); - public override void Reset(in AcSerializerOptions options) + public override void Reset(AcJsonSerializerOptions options) { _refTracker.Reset(); - if (ReferenceHandling != ReferenceHandlingMode.None) + if (options.ReferenceHandling != ReferenceHandlingMode.None) { _refTracker.EnsureInitialized(); } diff --git a/AyCode.Core/Serializers/SerializationContextBase.cs b/AyCode.Core/Serializers/SerializationContextBase.cs index e7d2d7b..21d08a5 100644 --- a/AyCode.Core/Serializers/SerializationContextBase.cs +++ b/AyCode.Core/Serializers/SerializationContextBase.cs @@ -11,8 +11,10 @@ namespace AyCode.Core.Serializers; /// Derived classes are sealed for JIT devirtualization (direct call speed). /// /// The concrete metadata type for serialization. -public abstract class SerializationContextBase : AcSerializerContextBase +/// The concrete options type. +public abstract class SerializationContextBase : AcSerializerContextBase where TMetadata : TypeMetadataBase + where TOptions : AcSerializerOptions { private const int BitArraySize = 1024; private const int MaxSmallId = BitArraySize * 64; @@ -103,7 +105,7 @@ public abstract class SerializationContextBase : AcSerializerContextB /// /// Resets serialization-specific state. Called by derived classes. /// - public override void Reset(in AcSerializerOptions options) + public override void Reset(TOptions options) { base.Reset(options); // Future: Reset serialization-specific state diff --git a/AyCode.Core/Serializers/Toons/AcToonSerializer.ToonSerializationContext.cs b/AyCode.Core/Serializers/Toons/AcToonSerializer.ToonSerializationContext.cs index 00c23cc..9636dff 100644 --- a/AyCode.Core/Serializers/Toons/AcToonSerializer.ToonSerializationContext.cs +++ b/AyCode.Core/Serializers/Toons/AcToonSerializer.ToonSerializationContext.cs @@ -49,7 +49,7 @@ public static partial class AcToonSerializer /// Pooled context for Toon serialization. /// Handles output building, indentation, and reference tracking. /// - private sealed class ToonSerializationContext : SerializationContextBase + private sealed class ToonSerializationContext : SerializationContextBase { private readonly StringBuilder _builder; private Dictionary? _scanOccurrences; @@ -58,13 +58,11 @@ public static partial class AcToonSerializer private HashSet? _registeredTypes; private int _nextRefId; - public AcToonSerializerOptions Options { get; private set; } public int CurrentIndentLevel { get; set; } public ToonSerializationContext(AcToonSerializerOptions options) { _builder = new StringBuilder(4096); - Options = options; Reset(options); } @@ -74,13 +72,12 @@ public static partial class AcToonSerializer protected override Func MetadataFactory => static t => new ToonSerializeTypeMetadata(t); - public void Reset(AcToonSerializerOptions options) + public override void Reset(AcToonSerializerOptions options) { - Options = options; CurrentIndentLevel = 0; _nextRefId = 1; - if (ReferenceHandling != ReferenceHandlingMode.None) + if (options.ReferenceHandling != ReferenceHandlingMode.None) { _scanOccurrences ??= new Dictionary(64, ReferenceEqualityComparer.Instance); _writtenRefs ??= new Dictionary(32, ReferenceEqualityComparer.Instance); diff --git a/AyCode.Core/Serializers/TypeMetadataBase.cs b/AyCode.Core/Serializers/TypeMetadataBase.cs index a44d35b..d77f2a5 100644 --- a/AyCode.Core/Serializers/TypeMetadataBase.cs +++ b/AyCode.Core/Serializers/TypeMetadataBase.cs @@ -1,10 +1,11 @@ +using AyCode.Core.Helpers; +using AyCode.Core.Serializers.Jsons; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; -using AyCode.Core.Helpers; using static AyCode.Core.Helpers.JsonUtilities; namespace AyCode.Core.Serializers; @@ -199,6 +200,12 @@ public abstract class TypeMetadataBase } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool UseReferenceHandling(ReferenceHandlingMode referenceHandling) + { + return referenceHandling != ReferenceHandlingMode.None && (IsIId || referenceHandling == ReferenceHandlingMode.All); + } + /// /// Gets serializable properties with stable property index ordering. /// Properties are ordered hierarchy-aware (base class first, then derived) diff --git a/AyCode.Core/Serializers/TypeMetadataWrapper.cs b/AyCode.Core/Serializers/TypeMetadataWrapper.cs index cb259de..8354620 100644 --- a/AyCode.Core/Serializers/TypeMetadataWrapper.cs +++ b/AyCode.Core/Serializers/TypeMetadataWrapper.cs @@ -1,6 +1,7 @@ using System; using System.Runtime.CompilerServices; using AyCode.Core.Helpers; +using AyCode.Core.Serializers.Jsons; namespace AyCode.Core.Serializers;