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.
This commit is contained in:
Loretta 2026-01-23 11:21:42 +01:00
parent cdf3cf34f8
commit 852ab53af3
10 changed files with 67 additions and 36 deletions

View File

@ -13,8 +13,16 @@ namespace AyCode.Core.Serializers;
/// GlobalMetadataCache stores metadata (thread-safe), wrappers store per-context tracking.
/// </summary>
/// <typeparam name="TMetadata">The concrete metadata type.</typeparam>
public abstract class AcSerializerContextBase<TMetadata> where TMetadata : TypeMetadataBase
/// <typeparam name="TOptions">The concrete options type.</typeparam>
public abstract class AcSerializerContextBase<TMetadata, TOptions>
where TMetadata : TypeMetadataBase
where TOptions : AcSerializerOptions
{
/// <summary>
/// The options used for this context. Set during Reset.
/// </summary>
public TOptions Options { get; private set; } = null!;
public byte MaxDepth { get; private set; }
public ReferenceHandlingMode ReferenceHandling { get; internal set; }
/// <summary>
@ -33,6 +41,19 @@ public abstract class AcSerializerContextBase<TMetadata> where TMetadata : TypeM
/// </summary>
protected abstract Func<Type, TMetadata> 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
/// <summary>
@ -119,15 +140,14 @@ public abstract class AcSerializerContextBase<TMetadata> where TMetadata : TypeM
/// Resets all wrapper tracking states for reuse.
/// Does not remove wrappers - keeps them for next operation.
/// </summary>
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;
}

View File

@ -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.
/// </summary>
internal sealed class BinaryDeserializationContextClass : AcSerializerContextBase<BinaryDeserializeTypeMetadata>
internal sealed class BinaryDeserializationContextClass : AcSerializerContextBase<BinaryDeserializeTypeMetadata, AcBinarySerializerOptions>
{
/// <summary>
/// Factory for creating BinaryDeserializeTypeMetadata instances.
@ -19,6 +19,12 @@ public static partial class AcBinaryDeserializer
protected override Func<Type, BinaryDeserializeTypeMetadata> MetadataFactory
=> static t => new BinaryDeserializeTypeMetadata(t);
public BinaryDeserializationContextClass()
{
// Initialize with default options - will be reset with actual options before use
Reset(AcBinarySerializerOptions.Default);
}
/// <summary>
/// After PopulateObject, checks if we should reuse an existing IId object.
/// Uses the unified tracking from AcSerializerContextBase (IdentityMap).

View File

@ -49,7 +49,7 @@ public static partial class AcBinarySerializer
/// <summary>
/// Binary serialization context. Public for generated serializers.
/// </summary>
internal sealed class BinarySerializationContext : SerializationContextBase<BinarySerializeTypeMetadata>, IDisposable
internal sealed class BinarySerializationContext : SerializationContextBase<BinarySerializeTypeMetadata, AcBinarySerializerOptions>, 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<Type, BinarySerializeTypeMetadata> 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)

View File

@ -9,8 +9,10 @@ namespace AyCode.Core.Serializers;
/// Derived classes are sealed for JIT devirtualization (direct call speed).
/// </summary>
/// <typeparam name="TMetadata">The concrete metadata type for deserialization.</typeparam>
public abstract class DeserializationContextBase<TMetadata> : AcSerializerContextBase<TMetadata>
/// <typeparam name="TOptions">The concrete options type.</typeparam>
public abstract class DeserializationContextBase<TMetadata, TOptions> : AcSerializerContextBase<TMetadata, TOptions>
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<TMetadata> : AcSerializerContex
/// <summary>
/// Resets deserialization-specific state. Called by derived classes.
/// </summary>
public override void Reset(in AcSerializerOptions options)
public override void Reset(TOptions options)
{
base.Reset(options);
// Future: Reset deserialization-specific state

View File

@ -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<JsonDeserializeTypeMetadata>
private sealed class DeserializationContext : AcSerializerContextBase<JsonDeserializeTypeMetadata, AcJsonSerializerOptions>
{
// 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();

View File

@ -35,7 +35,7 @@ public static partial class AcJsonSerializer
}
}
private sealed class JsonSerializationContext : SerializationContextBase<JsonSerializeTypeMetadata>, IDisposable
private sealed class JsonSerializationContext : SerializationContextBase<JsonSerializeTypeMetadata, AcJsonSerializerOptions>, IDisposable
{
private readonly ArrayBufferWriter<byte> _buffer;
public Utf8JsonWriter Writer { get; private set; }
@ -62,11 +62,11 @@ public static partial class AcJsonSerializer
protected override Func<Type, JsonSerializeTypeMetadata> 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();
}

View File

@ -11,8 +11,10 @@ namespace AyCode.Core.Serializers;
/// Derived classes are sealed for JIT devirtualization (direct call speed).
/// </summary>
/// <typeparam name="TMetadata">The concrete metadata type for serialization.</typeparam>
public abstract class SerializationContextBase<TMetadata> : AcSerializerContextBase<TMetadata>
/// <typeparam name="TOptions">The concrete options type.</typeparam>
public abstract class SerializationContextBase<TMetadata, TOptions> : AcSerializerContextBase<TMetadata, TOptions>
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<TMetadata> : AcSerializerContextB
/// <summary>
/// Resets serialization-specific state. Called by derived classes.
/// </summary>
public override void Reset(in AcSerializerOptions options)
public override void Reset(TOptions options)
{
base.Reset(options);
// Future: Reset serialization-specific state

View File

@ -49,7 +49,7 @@ public static partial class AcToonSerializer
/// Pooled context for Toon serialization.
/// Handles output building, indentation, and reference tracking.
/// </summary>
private sealed class ToonSerializationContext : SerializationContextBase<ToonSerializeTypeMetadata>
private sealed class ToonSerializationContext : SerializationContextBase<ToonSerializeTypeMetadata, AcToonSerializerOptions>
{
private readonly StringBuilder _builder;
private Dictionary<object, int>? _scanOccurrences;
@ -58,13 +58,11 @@ public static partial class AcToonSerializer
private HashSet<Type>? _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<Type, ToonSerializeTypeMetadata> 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<object, int>(64, ReferenceEqualityComparer.Instance);
_writtenRefs ??= new Dictionary<object, int>(32, ReferenceEqualityComparer.Instance);

View File

@ -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);
}
/// <summary>
/// Gets serializable properties with stable property index ordering.
/// Properties are ordered hierarchy-aware (base class first, then derived)

View File

@ -1,6 +1,7 @@
using System;
using System.Runtime.CompilerServices;
using AyCode.Core.Helpers;
using AyCode.Core.Serializers.Jsons;
namespace AyCode.Core.Serializers;