This is an automated email from the ASF dual-hosted git repository.

chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git


The following commit(s) were added to refs/heads/main by this push:
     new c8fd5ed5c refactor(c#): refactor serializer interface to minimize API 
surface (#3403)
c8fd5ed5c is described below

commit c8fd5ed5c3d463e5bdb772553a21d2ec4789af03
Author: Shawn Yang <[email protected]>
AuthorDate: Wed Feb 25 02:15:12 2026 +0800

    refactor(c#): refactor serializer interface to minimize API surface (#3403)
    
    ## Why?
    
    `Serializer<T>` had accumulated type metadata and object-dispatch
    responsibilities (type id, nullability, ref-tracking, type-info
    read/write, and object-level read/write). That expanded the public
    serializer API and duplicated logic across many serializer
    implementations.
    
    ## What does this PR do?
    
    - Removes the non-generic `Serializer` base and narrows `Serializer<T>`
    to typed read/write responsibilities.
    - Introduces richer `TypeInfo` metadata (built-in/user/dynamic kind,
    nullability, ref-trackability, default object, object-level read/write
    delegates, and field type-info decisions).
    - Refactors `TypeResolver` to operate on `TypeInfo` for registration,
    serializer factory creation/validation, wire-type resolution, and
    centralized type-info read/write.
    - Updates collection/dictionary/nullable-key/Any serialization flows and
    generator output to use `TypeInfo` + resolver-dispatched
    object/type-info operations.
    - Removes repeated `StaticTypeId`/nullability/ref-tracking/`IsNone`
    overrides across primitive, array, collection, dictionary, optional,
    string, time, enum, and union serializers.
    - Updates C# registration and peer test serializer call sites to the new
    type-info driven serializer contract.
    
    ## Related issues
    
    
    
    ## Does this PR introduce any user-facing change?
    
    
    
    - [x] Does this PR introduce any public API change?
    - [ ] Does this PR introduce any binary protocol compatibility change?
    
    ## Benchmark
---
 csharp/src/Fory.Generator/ForyObjectGenerator.cs  |  14 +-
 csharp/src/Fory/AnySerializer.cs                  |  41 +-
 csharp/src/Fory/CollectionSerializers.cs          |  61 +--
 csharp/src/Fory/DictionarySerializers.cs          |  61 +--
 csharp/src/Fory/EnumSerializer.cs                 |   1 -
 csharp/src/Fory/Fory.cs                           |  11 +-
 csharp/src/Fory/NullableKeyDictionary.cs          |  58 +--
 csharp/src/Fory/OptionalSerializer.cs             |  30 --
 csharp/src/Fory/PrimitiveArraySerializers.cs      |  50 ---
 csharp/src/Fory/PrimitiveCollectionSerializers.cs | 135 ------
 csharp/src/Fory/PrimitiveDictionarySerializers.cs |  20 -
 csharp/src/Fory/PrimitiveSerializers.cs           |  15 -
 csharp/src/Fory/Serializer.cs                     | 130 +-----
 csharp/src/Fory/StringSerializer.cs               |   4 -
 csharp/src/Fory/TimeSerializers.cs                |  24 --
 csharp/src/Fory/TypeId.cs                         |  19 -
 csharp/src/Fory/TypeInfo.cs                       | 450 +++++++++++++++++++-
 csharp/src/Fory/TypeResolver.cs                   | 492 ++++++++++++++--------
 csharp/src/Fory/UnionSerializer.cs                |   8 -
 csharp/tests/Fory.XlangPeer/Program.cs            |   4 -
 20 files changed, 872 insertions(+), 756 deletions(-)

diff --git a/csharp/src/Fory.Generator/ForyObjectGenerator.cs 
b/csharp/src/Fory.Generator/ForyObjectGenerator.cs
index 5d2cdb09a..808e6ef6b 100644
--- a/csharp/src/Fory.Generator/ForyObjectGenerator.cs
+++ b/csharp/src/Fory.Generator/ForyObjectGenerator.cs
@@ -135,7 +135,8 @@ public sealed class ForyObjectGenerator : 
IIncrementalGenerator
 
     private static void EmitObjectSerializer(StringBuilder sb, TypeModel model)
     {
-        sb.AppendLine($"file sealed class {model.SerializerName} : 
global::Apache.Fory.Serializer<{model.TypeName}>");
+        sb.AppendLine(
+            $"file sealed class {model.SerializerName} : 
global::Apache.Fory.Serializer<{model.TypeName}>");
         sb.AppendLine("{");
         foreach (MemberModel member in model.Members.Where(m => 
m.UseDictionaryTypeInfoCache))
         {
@@ -306,13 +307,9 @@ public sealed class ForyObjectGenerator : 
IIncrementalGenerator
         sb.AppendLine("        return value;");
         sb.AppendLine("    }");
         sb.AppendLine();
-        sb.AppendLine("    public override global::Apache.Fory.TypeId 
StaticTypeId => global::Apache.Fory.TypeId.Struct;");
         if (model.Kind == DeclKind.Class)
         {
-            sb.AppendLine("    public override bool IsNullableType => true;");
-            sb.AppendLine("    public override bool IsReferenceTrackableType 
=> true;");
             sb.AppendLine($"    public override {model.TypeName} DefaultValue 
=> null!;");
-            sb.AppendLine($"    public override bool IsNone(in 
{model.TypeName} value) => value is null;");
         }
         else
         {
@@ -320,7 +317,7 @@ public sealed class ForyObjectGenerator : 
IIncrementalGenerator
         }
 
         sb.AppendLine();
-        sb.AppendLine("    public override 
global::System.Collections.Generic.IReadOnlyList<global::Apache.Fory.TypeMetaFieldInfo>
 CompatibleTypeMetaFields(bool trackRef)");
+        sb.AppendLine("    public 
global::System.Collections.Generic.IReadOnlyList<global::Apache.Fory.TypeMetaFieldInfo>
 CompatibleTypeMetaFields(bool trackRef)");
         sb.AppendLine("    {");
         if (model.SortedMembers.Length == 0)
         {
@@ -438,7 +435,7 @@ public sealed class ForyObjectGenerator : 
IIncrementalGenerator
         string memberAccess = $"value.{member.Name}";
         string hasGenerics = member.IsCollection ? "true" : "false";
         string writeTypeInfo = compatibleMode
-            ? 
$"__ForyNeedsTypeInfoForField(context.TypeResolver.GetTypeInfo<{member.TypeName}>().StaticTypeId)"
+            ? 
$"context.TypeResolver.GetTypeInfo<{member.TypeName}>().NeedsTypeInfoForField()"
             : "false";
 
         switch (member.DynamicAnyKind)
@@ -516,7 +513,8 @@ public sealed class ForyObjectGenerator : 
IIncrementalGenerator
         sb.AppendLine($"                    __Fory{cacheId}DictRuntimeType = 
{runtimeTypeVar};");
         sb.AppendLine($"                    __Fory{cacheId}DictTypeInfo = 
{typeInfoVar};");
         sb.AppendLine("                }");
-        sb.AppendLine($"                {typeInfoVar}.WriteObject(context, 
{fieldValueVar}, {refModeExpr}, {writeTypeInfo}, {hasGenerics});");
+        sb.AppendLine(
+            $"                context.TypeResolver.WriteObject({typeInfoVar}, 
context, {fieldValueVar}, {refModeExpr}, {writeTypeInfo}, {hasGenerics});");
         sb.AppendLine("            }");
     }
 
diff --git a/csharp/src/Fory/AnySerializer.cs b/csharp/src/Fory/AnySerializer.cs
index 30df97111..77b6e80b2 100644
--- a/csharp/src/Fory/AnySerializer.cs
+++ b/csharp/src/Fory/AnySerializer.cs
@@ -19,15 +19,11 @@ namespace Apache.Fory;
 
 public sealed class DynamicAnyObjectSerializer : Serializer<object?>
 {
-    public override TypeId StaticTypeId => TypeId.Unknown;
-    public override bool IsNullableType => true;
-    public override bool IsReferenceTrackableType => true;
     public override object? DefaultValue => null;
-    public override bool IsNone(in object? value) => value is null;
 
     public override void WriteData(WriteContext context, in object? value, 
bool hasGenerics)
     {
-        if (IsNone(value))
+        if (value is null)
         {
             return;
         }
@@ -46,22 +42,11 @@ public sealed class DynamicAnyObjectSerializer : 
Serializer<object?>
         return context.TypeResolver.ReadDynamicValue(dynamicTypeInfo, context);
     }
 
-    public override void WriteTypeInfo(WriteContext context)
-    {
-        throw new InvalidDataException("dynamic Any value type info is 
runtime-only");
-    }
-
-    public override void ReadTypeInfo(ReadContext context)
-    {
-        DynamicTypeInfo typeInfo = 
context.TypeResolver.ReadDynamicTypeInfo(context);
-        context.SetDynamicTypeInfo(typeof(object), typeInfo);
-    }
-
     public override void Write(WriteContext context, in object? value, RefMode 
refMode, bool writeTypeInfo, bool hasGenerics)
     {
         if (refMode != RefMode.None)
         {
-            if (IsNone(value))
+            if (value is null)
             {
                 context.Writer.WriteInt8((sbyte)RefFlag.Null);
                 return;
@@ -113,7 +98,7 @@ public sealed class DynamicAnyObjectSerializer : 
Serializer<object?>
                     context.RefReader.PushPendingReference(reservedRefId);
                     if (readTypeInfo)
                     {
-                        ReadTypeInfo(context);
+                        ReadAnyTypeInfo(context);
                     }
 
                     object? value = ReadData(context);
@@ -135,7 +120,7 @@ public sealed class DynamicAnyObjectSerializer : 
Serializer<object?>
 
         if (readTypeInfo)
         {
-            ReadTypeInfo(context);
+            ReadAnyTypeInfo(context);
         }
 
         object? result = ReadData(context);
@@ -149,8 +134,14 @@ public sealed class DynamicAnyObjectSerializer : 
Serializer<object?>
 
     private static bool AnyValueIsReferenceTrackable(object value, 
TypeResolver typeResolver)
     {
-        Serializer serializer = typeResolver.GetSerializer(value.GetType());
-        return serializer.IsReferenceTrackableType;
+        TypeInfo typeInfo = typeResolver.GetTypeInfo(value.GetType());
+        return typeInfo.IsReferenceTrackableType;
+    }
+
+    private static void ReadAnyTypeInfo(ReadContext context)
+    {
+        DynamicTypeInfo typeInfo = 
context.TypeResolver.ReadDynamicTypeInfo(context);
+        context.SetDynamicTypeInfo(typeof(object), typeInfo);
     }
 }
 
@@ -169,8 +160,8 @@ public static class DynamicAnyCodec
             return;
         }
 
-        Serializer serializer = 
context.TypeResolver.GetSerializer(value.GetType());
-        serializer.WriteTypeInfo(context);
+        TypeInfo typeInfo = context.TypeResolver.GetTypeInfo(value.GetType());
+        context.TypeResolver.WriteTypeInfo(typeInfo, context);
     }
 
     public static object? CastAnyDynamicValue(object? value, Type targetType)
@@ -220,8 +211,8 @@ public static class DynamicAnyCodec
             return;
         }
 
-        Serializer serializer = 
context.TypeResolver.GetSerializer(value.GetType());
-        serializer.WriteDataObject(context, value, hasGenerics);
+        TypeInfo typeInfo = context.TypeResolver.GetTypeInfo(value.GetType());
+        context.TypeResolver.WriteDataObject(typeInfo, context, value, 
hasGenerics);
     }
 
     private static bool TryWriteKnownTypeInfo(object value, WriteContext 
context)
diff --git a/csharp/src/Fory/CollectionSerializers.cs 
b/csharp/src/Fory/CollectionSerializers.cs
index e5130c02c..5b1a04068 100644
--- a/csharp/src/Fory/CollectionSerializers.cs
+++ b/csharp/src/Fory/CollectionSerializers.cs
@@ -31,9 +31,14 @@ internal static class CollectionBits
 
 internal static class CollectionCodec
 {
-    private static bool CanDeclareElementType<T>(TypeId staticTypeId)
+    private static bool CanDeclareElementType<T>(TypeInfo typeInfo)
     {
-        if (!staticTypeId.NeedsTypeInfoForField())
+        if (typeInfo.IsBuiltinType)
+        {
+            return true;
+        }
+
+        if (!typeInfo.NeedsTypeInfoForField())
         {
             return true;
         }
@@ -47,6 +52,7 @@ internal static class CollectionCodec
         WriteContext context,
         bool hasGenerics)
     {
+        TypeInfo elementTypeInfo = context.TypeResolver.GetTypeInfo<T>();
         List<T> list = values as List<T> ?? [.. values];
         context.Writer.WriteVarUInt32((uint)list.Count);
         if (list.Count == 0)
@@ -55,11 +61,11 @@ internal static class CollectionCodec
         }
 
         bool hasNull = false;
-        if (elementSerializer.IsNullableType)
+        if (elementTypeInfo.IsNullableType)
         {
             for (int i = 0; i < list.Count; i++)
             {
-                if (!elementSerializer.IsNoneObject(list[i]))
+                if (!context.TypeResolver.IsNoneObject(elementTypeInfo, 
list[i]))
                 {
                     continue;
                 }
@@ -69,9 +75,9 @@ internal static class CollectionCodec
             }
         }
 
-        bool trackRef = context.TrackRef && 
elementSerializer.IsReferenceTrackableType;
-        bool declaredElementType = hasGenerics && 
CanDeclareElementType<T>(elementSerializer.StaticTypeId);
-        bool dynamicElementType = elementSerializer.StaticTypeId == 
TypeId.Unknown;
+        bool trackRef = context.TrackRef && 
elementTypeInfo.IsReferenceTrackableType;
+        bool declaredElementType = hasGenerics && 
CanDeclareElementType<T>(elementTypeInfo);
+        bool dynamicElementType = elementTypeInfo.IsDynamicType;
 
         byte header = dynamicElementType ? (byte)0 : CollectionBits.SameType;
         if (trackRef)
@@ -92,7 +98,7 @@ internal static class CollectionCodec
         context.Writer.WriteUInt8(header);
         if (!dynamicElementType && !declaredElementType)
         {
-            elementSerializer.WriteTypeInfo(context);
+            context.TypeResolver.WriteTypeInfo(elementSerializer, context);
         }
 
         if (dynamicElementType)
@@ -120,7 +126,7 @@ internal static class CollectionCodec
         {
             foreach (T element in list)
             {
-                if (elementSerializer.IsNoneObject(element))
+                if (context.TypeResolver.IsNoneObject(elementTypeInfo, 
element))
                 {
                     context.Writer.WriteInt8((sbyte)RefFlag.Null);
                 }
@@ -142,6 +148,7 @@ internal static class CollectionCodec
 
     public static List<T> ReadCollectionData<T>(Serializer<T> 
elementSerializer, ReadContext context)
     {
+        TypeInfo elementTypeInfo = context.TypeResolver.GetTypeInfo<T>();
         int length = checked((int)context.Reader.ReadVarUInt32());
         if (length == 0)
         {
@@ -153,7 +160,7 @@ internal static class CollectionCodec
         bool hasNull = (header & CollectionBits.HasNull) != 0;
         bool declared = (header & CollectionBits.DeclaredElementType) != 0;
         bool sameType = (header & CollectionBits.SameType) != 0;
-        bool canonicalizeElements = context.TrackRef && !trackRef && 
elementSerializer.IsReferenceTrackableType;
+        bool canonicalizeElements = context.TrackRef && !trackRef && 
elementTypeInfo.IsReferenceTrackableType;
 
         List<T> values = new(length);
         if (!sameType)
@@ -200,7 +207,7 @@ internal static class CollectionCodec
 
         if (!declared)
         {
-            elementSerializer.ReadTypeInfo(context);
+            context.TypeResolver.ReadTypeInfo(elementSerializer, context);
         }
 
         if (trackRef)
@@ -481,11 +488,7 @@ internal static class DynamicContainerCodec
 
 public sealed class ArraySerializer<T> : Serializer<T[]>
 {
-    public override TypeId StaticTypeId => TypeId.List;
-    public override bool IsNullableType => true;
-    public override bool IsReferenceTrackableType => true;
     public override T[] DefaultValue => null!;
-    public override bool IsNone(in T[] value) => value is null;
 
     public override void WriteData(WriteContext context, in T[] value, bool 
hasGenerics)
     {
@@ -506,11 +509,7 @@ public sealed class ArraySerializer<T> : Serializer<T[]>
 
 public class ListSerializer<T> : Serializer<List<T>>
 {
-    public override TypeId StaticTypeId => TypeId.List;
-    public override bool IsNullableType => true;
-    public override bool IsReferenceTrackableType => true;
     public override List<T> DefaultValue => null!;
-    public override bool IsNone(in List<T> value) => value is null;
 
     public override void WriteData(WriteContext context, in List<T> value, 
bool hasGenerics)
     {
@@ -526,11 +525,7 @@ public class ListSerializer<T> : Serializer<List<T>>
 
 public sealed class SetSerializer<T> : Serializer<HashSet<T>> where T : notnull
 {
-    public override TypeId StaticTypeId => TypeId.Set;
-    public override bool IsNullableType => true;
-    public override bool IsReferenceTrackableType => true;
     public override HashSet<T> DefaultValue => null!;
-    public override bool IsNone(in HashSet<T> value) => value is null;
 
     public override void WriteData(WriteContext context, in HashSet<T> value, 
bool hasGenerics)
     {
@@ -546,11 +541,7 @@ public sealed class SetSerializer<T> : 
Serializer<HashSet<T>> where T : notnull
 
 public sealed class SortedSetSerializer<T> : Serializer<SortedSet<T>> where T 
: notnull
 {
-    public override TypeId StaticTypeId => TypeId.Set;
-    public override bool IsNullableType => true;
-    public override bool IsReferenceTrackableType => true;
     public override SortedSet<T> DefaultValue => null!;
-    public override bool IsNone(in SortedSet<T> value) => value is null;
 
     public override void WriteData(WriteContext context, in SortedSet<T> 
value, bool hasGenerics)
     {
@@ -566,11 +557,7 @@ public sealed class SortedSetSerializer<T> : 
Serializer<SortedSet<T>> where T :
 
 public sealed class ImmutableHashSetSerializer<T> : 
Serializer<ImmutableHashSet<T>> where T : notnull
 {
-    public override TypeId StaticTypeId => TypeId.Set;
-    public override bool IsNullableType => true;
-    public override bool IsReferenceTrackableType => true;
     public override ImmutableHashSet<T> DefaultValue => null!;
-    public override bool IsNone(in ImmutableHashSet<T> value) => value is null;
 
     public override void WriteData(WriteContext context, in 
ImmutableHashSet<T> value, bool hasGenerics)
     {
@@ -586,11 +573,7 @@ public sealed class ImmutableHashSetSerializer<T> : 
Serializer<ImmutableHashSet<
 
 public sealed class LinkedListSerializer<T> : Serializer<LinkedList<T>>
 {
-    public override TypeId StaticTypeId => TypeId.List;
-    public override bool IsNullableType => true;
-    public override bool IsReferenceTrackableType => true;
     public override LinkedList<T> DefaultValue => null!;
-    public override bool IsNone(in LinkedList<T> value) => value is null;
 
     public override void WriteData(WriteContext context, in LinkedList<T> 
value, bool hasGenerics)
     {
@@ -606,11 +589,7 @@ public sealed class LinkedListSerializer<T> : 
Serializer<LinkedList<T>>
 
 public sealed class QueueSerializer<T> : Serializer<Queue<T>>
 {
-    public override TypeId StaticTypeId => TypeId.List;
-    public override bool IsNullableType => true;
-    public override bool IsReferenceTrackableType => true;
     public override Queue<T> DefaultValue => null!;
-    public override bool IsNone(in Queue<T> value) => value is null;
 
     public override void WriteData(WriteContext context, in Queue<T> value, 
bool hasGenerics)
     {
@@ -633,11 +612,7 @@ public sealed class QueueSerializer<T> : 
Serializer<Queue<T>>
 
 public sealed class StackSerializer<T> : Serializer<Stack<T>>
 {
-    public override TypeId StaticTypeId => TypeId.List;
-    public override bool IsNullableType => true;
-    public override bool IsReferenceTrackableType => true;
     public override Stack<T> DefaultValue => null!;
-    public override bool IsNone(in Stack<T> value) => value is null;
 
     public override void WriteData(WriteContext context, in Stack<T> value, 
bool hasGenerics)
     {
diff --git a/csharp/src/Fory/DictionarySerializers.cs 
b/csharp/src/Fory/DictionarySerializers.cs
index 703526767..289435549 100644
--- a/csharp/src/Fory/DictionarySerializers.cs
+++ b/csharp/src/Fory/DictionarySerializers.cs
@@ -33,11 +33,7 @@ public abstract class DictionaryLikeSerializer<TDictionary, 
TKey, TValue> : Seri
     where TDictionary : class, IDictionary<TKey, TValue>
     where TKey : notnull
 {
-    public override TypeId StaticTypeId => TypeId.Map;
-    public override bool IsNullableType => true;
-    public override bool IsReferenceTrackableType => true;
     public override TDictionary DefaultValue => null!;
-    public override bool IsNone(in TDictionary value) => value is null;
 
     protected abstract TDictionary CreateMap(int capacity);
 
@@ -55,6 +51,8 @@ public abstract class DictionaryLikeSerializer<TDictionary, 
TKey, TValue> : Seri
     {
         Serializer<TKey> keySerializer = 
context.TypeResolver.GetSerializer<TKey>();
         Serializer<TValue> valueSerializer = 
context.TypeResolver.GetSerializer<TValue>();
+        TypeInfo keyTypeInfo = context.TypeResolver.GetTypeInfo<TKey>();
+        TypeInfo valueTypeInfo = context.TypeResolver.GetTypeInfo<TValue>();
         TDictionary map = value ?? CreateMap(0);
         context.Writer.WriteVarUInt32((uint)map.Count);
         if (map.Count == 0)
@@ -62,12 +60,12 @@ public abstract class DictionaryLikeSerializer<TDictionary, 
TKey, TValue> : Seri
             return;
         }
 
-        bool trackKeyRef = context.TrackRef && 
keySerializer.IsReferenceTrackableType;
-        bool trackValueRef = context.TrackRef && 
valueSerializer.IsReferenceTrackableType;
-        bool keyDeclared = hasGenerics && 
!keySerializer.StaticTypeId.NeedsTypeInfoForField();
-        bool valueDeclared = hasGenerics && 
!valueSerializer.StaticTypeId.NeedsTypeInfoForField();
-        bool keyDynamicType = keySerializer.StaticTypeId == TypeId.Unknown;
-        bool valueDynamicType = valueSerializer.StaticTypeId == TypeId.Unknown;
+        bool trackKeyRef = context.TrackRef && 
keyTypeInfo.IsReferenceTrackableType;
+        bool trackValueRef = context.TrackRef && 
valueTypeInfo.IsReferenceTrackableType;
+        bool keyDeclared = hasGenerics && !keyTypeInfo.NeedsTypeInfoForField();
+        bool valueDeclared = hasGenerics && 
!valueTypeInfo.NeedsTypeInfoForField();
+        bool keyDynamicType = keyTypeInfo.IsDynamicType;
+        bool valueDynamicType = valueTypeInfo.IsDynamicType;
 
         KeyValuePair<TKey, TValue>[] pairs = SnapshotPairs(map);
         if (keyDynamicType || valueDynamicType)
@@ -82,6 +80,8 @@ public abstract class DictionaryLikeSerializer<TDictionary, 
TKey, TValue> : Seri
                 valueDeclared,
                 keyDynamicType,
                 valueDynamicType,
+                keyTypeInfo,
+                valueTypeInfo,
                 keySerializer,
                 valueSerializer);
             return;
@@ -91,8 +91,8 @@ public abstract class DictionaryLikeSerializer<TDictionary, 
TKey, TValue> : Seri
         while (index < pairs.Length)
         {
             KeyValuePair<TKey, TValue> pair = pairs[index];
-            bool keyIsNull = keySerializer.IsNoneObject(pair.Key);
-            bool valueIsNull = valueSerializer.IsNoneObject(pair.Value);
+            bool keyIsNull = context.TypeResolver.IsNoneObject(keyTypeInfo, 
pair.Key);
+            bool valueIsNull = 
context.TypeResolver.IsNoneObject(valueTypeInfo, pair.Value);
             if (keyIsNull || valueIsNull)
             {
                 byte header = 0;
@@ -131,7 +131,7 @@ public abstract class DictionaryLikeSerializer<TDictionary, 
TKey, TValue> : Seri
                 {
                     if (!keyDeclared)
                     {
-                        keySerializer.WriteTypeInfo(context);
+                        context.TypeResolver.WriteTypeInfo(keySerializer, 
context);
                     }
 
                     keySerializer.Write(context, pair.Key, trackKeyRef ? 
RefMode.Tracking : RefMode.None, false, hasGenerics);
@@ -141,7 +141,7 @@ public abstract class DictionaryLikeSerializer<TDictionary, 
TKey, TValue> : Seri
                 {
                     if (!valueDeclared)
                     {
-                        valueSerializer.WriteTypeInfo(context);
+                        context.TypeResolver.WriteTypeInfo(valueSerializer, 
context);
                     }
 
                     valueSerializer.Write(context, pair.Value, trackValueRef ? 
RefMode.Tracking : RefMode.None, false, hasGenerics);
@@ -177,19 +177,20 @@ public abstract class 
DictionaryLikeSerializer<TDictionary, TKey, TValue> : Seri
             context.Writer.WriteUInt8(0);
             if (!keyDeclared)
             {
-                keySerializer.WriteTypeInfo(context);
+                context.TypeResolver.WriteTypeInfo(keySerializer, context);
             }
 
             if (!valueDeclared)
             {
-                valueSerializer.WriteTypeInfo(context);
+                context.TypeResolver.WriteTypeInfo(valueSerializer, context);
             }
 
             byte chunkSize = 0;
             while (index < pairs.Length && chunkSize < byte.MaxValue)
             {
                 KeyValuePair<TKey, TValue> current = pairs[index];
-                if (keySerializer.IsNoneObject(current.Key) || 
valueSerializer.IsNoneObject(current.Value))
+                if (context.TypeResolver.IsNoneObject(keyTypeInfo, 
current.Key) ||
+                    context.TypeResolver.IsNoneObject(valueTypeInfo, 
current.Value))
                 {
                     break;
                 }
@@ -208,6 +209,8 @@ public abstract class DictionaryLikeSerializer<TDictionary, 
TKey, TValue> : Seri
     {
         Serializer<TKey> keySerializer = 
context.TypeResolver.GetSerializer<TKey>();
         Serializer<TValue> valueSerializer = 
context.TypeResolver.GetSerializer<TValue>();
+        TypeInfo keyTypeInfo = context.TypeResolver.GetTypeInfo<TKey>();
+        TypeInfo valueTypeInfo = context.TypeResolver.GetTypeInfo<TValue>();
         int totalLength = checked((int)context.Reader.ReadVarUInt32());
         if (totalLength == 0)
         {
@@ -215,9 +218,9 @@ public abstract class DictionaryLikeSerializer<TDictionary, 
TKey, TValue> : Seri
         }
 
         TDictionary map = CreateMap(totalLength);
-        bool keyDynamicType = keySerializer.StaticTypeId == TypeId.Unknown;
-        bool valueDynamicType = valueSerializer.StaticTypeId == TypeId.Unknown;
-        bool canonicalizeValues = context.TrackRef && 
valueSerializer.IsReferenceTrackableType;
+        bool keyDynamicType = keyTypeInfo.IsDynamicType;
+        bool valueDynamicType = valueTypeInfo.IsDynamicType;
+        bool canonicalizeValues = context.TrackRef && 
valueTypeInfo.IsReferenceTrackableType;
 
         int readCount = 0;
         while (readCount < totalLength)
@@ -280,7 +283,7 @@ public abstract class DictionaryLikeSerializer<TDictionary, 
TKey, TValue> : Seri
                         }
                         else
                         {
-                            keySerializer.ReadTypeInfo(context);
+                            context.TypeResolver.ReadTypeInfo(keySerializer, 
context);
                         }
                     }
 
@@ -292,7 +295,7 @@ public abstract class DictionaryLikeSerializer<TDictionary, 
TKey, TValue> : Seri
                         }
                         else
                         {
-                            valueSerializer.ReadTypeInfo(context);
+                            context.TypeResolver.ReadTypeInfo(valueSerializer, 
context);
                         }
                     }
 
@@ -332,12 +335,12 @@ public abstract class 
DictionaryLikeSerializer<TDictionary, TKey, TValue> : Seri
 
             if (!keyDeclared)
             {
-                keySerializer.ReadTypeInfo(context);
+                context.TypeResolver.ReadTypeInfo(keySerializer, context);
             }
 
             if (!valueDeclared)
             {
-                valueSerializer.ReadTypeInfo(context);
+                context.TypeResolver.ReadTypeInfo(valueSerializer, context);
             }
 
             for (int i = 0; i < chunkSize; i++)
@@ -373,13 +376,15 @@ public abstract class 
DictionaryLikeSerializer<TDictionary, TKey, TValue> : Seri
         bool valueDeclared,
         bool keyDynamicType,
         bool valueDynamicType,
+        TypeInfo keyTypeInfo,
+        TypeInfo valueTypeInfo,
         Serializer<TKey> keySerializer,
         Serializer<TValue> valueSerializer)
     {
         foreach (KeyValuePair<TKey, TValue> pair in pairs)
         {
-            bool keyIsNull = keySerializer.IsNoneObject(pair.Key);
-            bool valueIsNull = valueSerializer.IsNoneObject(pair.Value);
+            bool keyIsNull = context.TypeResolver.IsNoneObject(keyTypeInfo, 
pair.Key);
+            bool valueIsNull = 
context.TypeResolver.IsNoneObject(valueTypeInfo, pair.Value);
             byte header = 0;
             if (trackKeyRef)
             {
@@ -446,7 +451,7 @@ public abstract class DictionaryLikeSerializer<TDictionary, 
TKey, TValue> : Seri
                 }
                 else
                 {
-                    keySerializer.WriteTypeInfo(context);
+                    context.TypeResolver.WriteTypeInfo(keySerializer, context);
                 }
             }
 
@@ -458,7 +463,7 @@ public abstract class DictionaryLikeSerializer<TDictionary, 
TKey, TValue> : Seri
                 }
                 else
                 {
-                    valueSerializer.WriteTypeInfo(context);
+                    context.TypeResolver.WriteTypeInfo(valueSerializer, 
context);
                 }
             }
 
diff --git a/csharp/src/Fory/EnumSerializer.cs 
b/csharp/src/Fory/EnumSerializer.cs
index e830f0756..a7cff943e 100644
--- a/csharp/src/Fory/EnumSerializer.cs
+++ b/csharp/src/Fory/EnumSerializer.cs
@@ -22,7 +22,6 @@ public sealed class EnumSerializer<TEnum> : Serializer<TEnum> 
where TEnum : stru
     private static readonly Dictionary<TEnum, uint> DefinedValueToOrdinal = 
BuildValueToOrdinalMap();
     private static readonly Dictionary<uint, TEnum> DefinedOrdinalToValue = 
BuildOrdinalToValueMap(DefinedValueToOrdinal);
 
-    public override TypeId StaticTypeId => TypeId.Enum;
     public override TEnum DefaultValue => default;
 
     public override void WriteData(WriteContext context, in TEnum value, bool 
hasGenerics)
diff --git a/csharp/src/Fory/Fory.cs b/csharp/src/Fory/Fory.cs
index d70cef7dd..412c17c61 100644
--- a/csharp/src/Fory/Fory.cs
+++ b/csharp/src/Fory/Fory.cs
@@ -80,16 +80,16 @@ public sealed class Fory
     public Fory Register<T, TSerializer>(uint typeId)
         where TSerializer : Serializer<T>, new()
     {
-        Serializer serializer = _typeResolver.RegisterSerializer<T, 
TSerializer>();
-        _typeResolver.Register(typeof(T), typeId, serializer);
+        TypeInfo typeInfo = _typeResolver.RegisterSerializer<T, TSerializer>();
+        _typeResolver.Register(typeof(T), typeId, typeInfo);
         return this;
     }
 
     public Fory Register<T, TSerializer>(string typeNamespace, string typeName)
         where TSerializer : Serializer<T>, new()
     {
-        Serializer serializer = _typeResolver.RegisterSerializer<T, 
TSerializer>();
-        _typeResolver.Register(typeof(T), typeNamespace, typeName, serializer);
+        TypeInfo typeInfo = _typeResolver.RegisterSerializer<T, TSerializer>();
+        _typeResolver.Register(typeof(T), typeNamespace, typeName, typeInfo);
         return this;
     }
 
@@ -98,7 +98,8 @@ public sealed class Fory
         ByteWriter writer = _writeContext.Writer;
         writer.Reset();
         Serializer<T> serializer = _typeResolver.GetSerializer<T>();
-        bool isNone = serializer.IsNone(value);
+        TypeInfo typeInfo = _typeResolver.GetTypeInfo<T>();
+        bool isNone = typeInfo.IsNullableType && value is null;
         WriteHead(writer, isNone);
         if (!isNone)
         {
diff --git a/csharp/src/Fory/NullableKeyDictionary.cs 
b/csharp/src/Fory/NullableKeyDictionary.cs
index fc1c0ae6c..544500da8 100644
--- a/csharp/src/Fory/NullableKeyDictionary.cs
+++ b/csharp/src/Fory/NullableKeyDictionary.cs
@@ -390,16 +390,14 @@ public sealed class NullableKeyDictionary<TKey, TValue> : 
IDictionary<TKey, TVal
 
 public sealed class NullableKeyDictionarySerializer<TKey, TValue> : 
Serializer<NullableKeyDictionary<TKey, TValue>>
 {
-    public override TypeId StaticTypeId => TypeId.Map;
-    public override bool IsNullableType => true;
-    public override bool IsReferenceTrackableType => true;
     public override NullableKeyDictionary<TKey, TValue> DefaultValue => null!;
-    public override bool IsNone(in NullableKeyDictionary<TKey, TValue> value) 
=> value is null;
 
     public override void WriteData(WriteContext context, in 
NullableKeyDictionary<TKey, TValue> value, bool hasGenerics)
     {
         Serializer<TKey> keySerializer = 
context.TypeResolver.GetSerializer<TKey>();
         Serializer<TValue> valueSerializer = 
context.TypeResolver.GetSerializer<TValue>();
+        TypeInfo keyTypeInfo = context.TypeResolver.GetTypeInfo<TKey>();
+        TypeInfo valueTypeInfo = context.TypeResolver.GetTypeInfo<TValue>();
         NullableKeyDictionary<TKey, TValue> map = value ?? new 
NullableKeyDictionary<TKey, TValue>();
         context.Writer.WriteVarUInt32((uint)map.Count);
         if (map.Count == 0)
@@ -407,12 +405,12 @@ public sealed class NullableKeyDictionarySerializer<TKey, 
TValue> : Serializer<N
             return;
         }
 
-        bool trackKeyRef = context.TrackRef && 
keySerializer.IsReferenceTrackableType;
-        bool trackValueRef = context.TrackRef && 
valueSerializer.IsReferenceTrackableType;
-        bool keyDeclared = hasGenerics && 
!keySerializer.StaticTypeId.NeedsTypeInfoForField();
-        bool valueDeclared = hasGenerics && 
!valueSerializer.StaticTypeId.NeedsTypeInfoForField();
-        bool keyDynamicType = keySerializer.StaticTypeId == TypeId.Unknown;
-        bool valueDynamicType = valueSerializer.StaticTypeId == TypeId.Unknown;
+        bool trackKeyRef = context.TrackRef && 
keyTypeInfo.IsReferenceTrackableType;
+        bool trackValueRef = context.TrackRef && 
valueTypeInfo.IsReferenceTrackableType;
+        bool keyDeclared = hasGenerics && !keyTypeInfo.NeedsTypeInfoForField();
+        bool valueDeclared = hasGenerics && 
!valueTypeInfo.NeedsTypeInfoForField();
+        bool keyDynamicType = keyTypeInfo.IsDynamicType;
+        bool valueDynamicType = valueTypeInfo.IsDynamicType;
         KeyValuePair<TKey, TValue>[] pairs = [.. map];
         if (keyDynamicType || valueDynamicType)
         {
@@ -426,6 +424,8 @@ public sealed class NullableKeyDictionarySerializer<TKey, 
TValue> : Serializer<N
                 valueDeclared,
                 keyDynamicType,
                 valueDynamicType,
+                keyTypeInfo,
+                valueTypeInfo,
                 keySerializer,
                 valueSerializer);
             return;
@@ -433,8 +433,8 @@ public sealed class NullableKeyDictionarySerializer<TKey, 
TValue> : Serializer<N
 
         foreach (KeyValuePair<TKey, TValue> entry in pairs)
         {
-            bool keyIsNull = entry.Key is null || 
keySerializer.IsNoneObject(entry.Key);
-            bool valueIsNull = valueSerializer.IsNoneObject(entry.Value);
+            bool keyIsNull = context.TypeResolver.IsNoneObject(keyTypeInfo, 
entry.Key);
+            bool valueIsNull = 
context.TypeResolver.IsNoneObject(valueTypeInfo, entry.Value);
             byte header = 0;
             if (trackKeyRef)
             {
@@ -474,7 +474,7 @@ public sealed class NullableKeyDictionarySerializer<TKey, 
TValue> : Serializer<N
             {
                 if (!valueDeclared)
                 {
-                    valueSerializer.WriteTypeInfo(context);
+                    context.TypeResolver.WriteTypeInfo(valueSerializer, 
context);
                 }
 
                 valueSerializer.Write(
@@ -490,7 +490,7 @@ public sealed class NullableKeyDictionarySerializer<TKey, 
TValue> : Serializer<N
             {
                 if (!keyDeclared)
                 {
-                    keySerializer.WriteTypeInfo(context);
+                    context.TypeResolver.WriteTypeInfo(keySerializer, context);
                 }
 
                 keySerializer.Write(
@@ -505,12 +505,12 @@ public sealed class NullableKeyDictionarySerializer<TKey, 
TValue> : Serializer<N
             context.Writer.WriteUInt8(1);
             if (!keyDeclared)
             {
-                keySerializer.WriteTypeInfo(context);
+                context.TypeResolver.WriteTypeInfo(keySerializer, context);
             }
 
             if (!valueDeclared)
             {
-                valueSerializer.WriteTypeInfo(context);
+                context.TypeResolver.WriteTypeInfo(valueSerializer, context);
             }
 
             keySerializer.Write(
@@ -532,6 +532,8 @@ public sealed class NullableKeyDictionarySerializer<TKey, 
TValue> : Serializer<N
     {
         Serializer<TKey> keySerializer = 
context.TypeResolver.GetSerializer<TKey>();
         Serializer<TValue> valueSerializer = 
context.TypeResolver.GetSerializer<TValue>();
+        TypeInfo keyTypeInfo = context.TypeResolver.GetTypeInfo<TKey>();
+        TypeInfo valueTypeInfo = context.TypeResolver.GetTypeInfo<TValue>();
         int totalLength = checked((int)context.Reader.ReadVarUInt32());
         if (totalLength == 0)
         {
@@ -539,9 +541,9 @@ public sealed class NullableKeyDictionarySerializer<TKey, 
TValue> : Serializer<N
         }
 
         NullableKeyDictionary<TKey, TValue> map = new();
-        bool keyDynamicType = keySerializer.StaticTypeId == TypeId.Unknown;
-        bool valueDynamicType = valueSerializer.StaticTypeId == TypeId.Unknown;
-        bool canonicalizeValues = context.TrackRef && 
valueSerializer.IsReferenceTrackableType;
+        bool keyDynamicType = keyTypeInfo.IsDynamicType;
+        bool valueDynamicType = valueTypeInfo.IsDynamicType;
+        bool canonicalizeValues = context.TrackRef && 
valueTypeInfo.IsReferenceTrackableType;
 
         int readCount = 0;
         while (readCount < totalLength)
@@ -603,7 +605,7 @@ public sealed class NullableKeyDictionarySerializer<TKey, 
TValue> : Serializer<N
                         }
                         else
                         {
-                            keySerializer.ReadTypeInfo(context);
+                            context.TypeResolver.ReadTypeInfo(keySerializer, 
context);
                         }
                     }
 
@@ -615,7 +617,7 @@ public sealed class NullableKeyDictionarySerializer<TKey, 
TValue> : Serializer<N
                         }
                         else
                         {
-                            valueSerializer.ReadTypeInfo(context);
+                            context.TypeResolver.ReadTypeInfo(valueSerializer, 
context);
                         }
                     }
 
@@ -655,12 +657,12 @@ public sealed class NullableKeyDictionarySerializer<TKey, 
TValue> : Serializer<N
 
             if (!keyDeclared)
             {
-                keySerializer.ReadTypeInfo(context);
+                context.TypeResolver.ReadTypeInfo(keySerializer, context);
             }
 
             if (!valueDeclared)
             {
-                valueSerializer.ReadTypeInfo(context);
+                context.TypeResolver.ReadTypeInfo(valueSerializer, context);
             }
 
             for (int i = 0; i < chunkSize; i++)
@@ -696,13 +698,15 @@ public sealed class NullableKeyDictionarySerializer<TKey, 
TValue> : Serializer<N
         bool valueDeclared,
         bool keyDynamicType,
         bool valueDynamicType,
+        TypeInfo keyTypeInfo,
+        TypeInfo valueTypeInfo,
         Serializer<TKey> keySerializer,
         Serializer<TValue> valueSerializer)
     {
         foreach (KeyValuePair<TKey, TValue> pair in pairs)
         {
-            bool keyIsNull = pair.Key is null || 
keySerializer.IsNoneObject(pair.Key);
-            bool valueIsNull = valueSerializer.IsNoneObject(pair.Value);
+            bool keyIsNull = context.TypeResolver.IsNoneObject(keyTypeInfo, 
pair.Key);
+            bool valueIsNull = 
context.TypeResolver.IsNoneObject(valueTypeInfo, pair.Value);
             byte header = 0;
             if (trackKeyRef)
             {
@@ -769,7 +773,7 @@ public sealed class NullableKeyDictionarySerializer<TKey, 
TValue> : Serializer<N
                 }
                 else
                 {
-                    keySerializer.WriteTypeInfo(context);
+                    context.TypeResolver.WriteTypeInfo(keySerializer, context);
                 }
             }
 
@@ -781,7 +785,7 @@ public sealed class NullableKeyDictionarySerializer<TKey, 
TValue> : Serializer<N
                 }
                 else
                 {
-                    valueSerializer.WriteTypeInfo(context);
+                    context.TypeResolver.WriteTypeInfo(valueSerializer, 
context);
                 }
             }
 
diff --git a/csharp/src/Fory/OptionalSerializer.cs 
b/csharp/src/Fory/OptionalSerializer.cs
index 0f638bdb3..9cb42c7eb 100644
--- a/csharp/src/Fory/OptionalSerializer.cs
+++ b/csharp/src/Fory/OptionalSerializer.cs
@@ -19,21 +19,8 @@ namespace Apache.Fory;
 
 public sealed class NullableSerializer<T> : Serializer<T?> where T : struct
 {
-    private readonly Serializer<T> _defaultWrappedSerializer = new 
TypeResolver().GetSerializer<T>();
-
-    public override TypeId StaticTypeId => 
_defaultWrappedSerializer.StaticTypeId;
-
-    public override bool IsNullableType => true;
-
-    public override bool IsReferenceTrackableType => 
_defaultWrappedSerializer.IsReferenceTrackableType;
-
     public override T? DefaultValue => null;
 
-    public override bool IsNone(in T? value)
-    {
-        return !value.HasValue;
-    }
-
     public override void WriteData(WriteContext context, in T? value, bool 
hasGenerics)
     {
         if (!value.HasValue)
@@ -52,23 +39,6 @@ public sealed class NullableSerializer<T> : Serializer<T?> 
where T : struct
         return wrappedSerializer.ReadData(context);
     }
 
-    public override void WriteTypeInfo(WriteContext context)
-    {
-        Serializer<T> wrappedSerializer = 
context.TypeResolver.GetSerializer<T>();
-        wrappedSerializer.WriteTypeInfo(context);
-    }
-
-    public override void ReadTypeInfo(ReadContext context)
-    {
-        Serializer<T> wrappedSerializer = 
context.TypeResolver.GetSerializer<T>();
-        wrappedSerializer.ReadTypeInfo(context);
-    }
-
-    public override IReadOnlyList<TypeMetaFieldInfo> 
CompatibleTypeMetaFields(bool trackRef)
-    {
-        return _defaultWrappedSerializer.CompatibleTypeMetaFields(trackRef);
-    }
-
     public override void Write(WriteContext context, in T? value, RefMode 
refMode, bool writeTypeInfo, bool hasGenerics)
     {
         Serializer<T> wrappedSerializer = 
context.TypeResolver.GetSerializer<T>();
diff --git a/csharp/src/Fory/PrimitiveArraySerializers.cs 
b/csharp/src/Fory/PrimitiveArraySerializers.cs
index e2882c8e0..505ec0dab 100644
--- a/csharp/src/Fory/PrimitiveArraySerializers.cs
+++ b/csharp/src/Fory/PrimitiveArraySerializers.cs
@@ -19,16 +19,11 @@ namespace Apache.Fory;
 
 internal sealed class BoolArraySerializer : Serializer<bool[]>
 {
-    public override TypeId StaticTypeId => TypeId.BoolArray;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override bool[] DefaultValue => null!;
 
-    public override bool IsNone(in bool[] value) => value is null;
-
     public override void WriteData(WriteContext context, in bool[] value, bool 
hasGenerics)
     {
         _ = hasGenerics;
@@ -55,16 +50,11 @@ internal sealed class BoolArraySerializer : 
Serializer<bool[]>
 
 internal sealed class Int8ArraySerializer : Serializer<sbyte[]>
 {
-    public override TypeId StaticTypeId => TypeId.Int8Array;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override sbyte[] DefaultValue => null!;
 
-    public override bool IsNone(in sbyte[] value) => value is null;
-
     public override void WriteData(WriteContext context, in sbyte[] value, 
bool hasGenerics)
     {
         _ = hasGenerics;
@@ -91,16 +81,11 @@ internal sealed class Int8ArraySerializer : 
Serializer<sbyte[]>
 
 internal sealed class Int16ArraySerializer : Serializer<short[]>
 {
-    public override TypeId StaticTypeId => TypeId.Int16Array;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override short[] DefaultValue => null!;
 
-    public override bool IsNone(in short[] value) => value is null;
-
     public override void WriteData(WriteContext context, in short[] value, 
bool hasGenerics)
     {
         _ = hasGenerics;
@@ -132,16 +117,11 @@ internal sealed class Int16ArraySerializer : 
Serializer<short[]>
 
 internal sealed class Int32ArraySerializer : Serializer<int[]>
 {
-    public override TypeId StaticTypeId => TypeId.Int32Array;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override int[] DefaultValue => null!;
 
-    public override bool IsNone(in int[] value) => value is null;
-
     public override void WriteData(WriteContext context, in int[] value, bool 
hasGenerics)
     {
         _ = hasGenerics;
@@ -173,16 +153,11 @@ internal sealed class Int32ArraySerializer : 
Serializer<int[]>
 
 internal sealed class Int64ArraySerializer : Serializer<long[]>
 {
-    public override TypeId StaticTypeId => TypeId.Int64Array;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override long[] DefaultValue => null!;
 
-    public override bool IsNone(in long[] value) => value is null;
-
     public override void WriteData(WriteContext context, in long[] value, bool 
hasGenerics)
     {
         _ = hasGenerics;
@@ -214,16 +189,11 @@ internal sealed class Int64ArraySerializer : 
Serializer<long[]>
 
 internal sealed class UInt16ArraySerializer : Serializer<ushort[]>
 {
-    public override TypeId StaticTypeId => TypeId.UInt16Array;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override ushort[] DefaultValue => null!;
 
-    public override bool IsNone(in ushort[] value) => value is null;
-
     public override void WriteData(WriteContext context, in ushort[] value, 
bool hasGenerics)
     {
         _ = hasGenerics;
@@ -255,16 +225,11 @@ internal sealed class UInt16ArraySerializer : 
Serializer<ushort[]>
 
 internal sealed class UInt32ArraySerializer : Serializer<uint[]>
 {
-    public override TypeId StaticTypeId => TypeId.UInt32Array;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override uint[] DefaultValue => null!;
 
-    public override bool IsNone(in uint[] value) => value is null;
-
     public override void WriteData(WriteContext context, in uint[] value, bool 
hasGenerics)
     {
         _ = hasGenerics;
@@ -296,16 +261,11 @@ internal sealed class UInt32ArraySerializer : 
Serializer<uint[]>
 
 internal sealed class UInt64ArraySerializer : Serializer<ulong[]>
 {
-    public override TypeId StaticTypeId => TypeId.UInt64Array;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override ulong[] DefaultValue => null!;
 
-    public override bool IsNone(in ulong[] value) => value is null;
-
     public override void WriteData(WriteContext context, in ulong[] value, 
bool hasGenerics)
     {
         _ = hasGenerics;
@@ -337,16 +297,11 @@ internal sealed class UInt64ArraySerializer : 
Serializer<ulong[]>
 
 internal sealed class Float32ArraySerializer : Serializer<float[]>
 {
-    public override TypeId StaticTypeId => TypeId.Float32Array;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override float[] DefaultValue => null!;
 
-    public override bool IsNone(in float[] value) => value is null;
-
     public override void WriteData(WriteContext context, in float[] value, 
bool hasGenerics)
     {
         _ = hasGenerics;
@@ -378,16 +333,11 @@ internal sealed class Float32ArraySerializer : 
Serializer<float[]>
 
 internal sealed class Float64ArraySerializer : Serializer<double[]>
 {
-    public override TypeId StaticTypeId => TypeId.Float64Array;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override double[] DefaultValue => null!;
 
-    public override bool IsNone(in double[] value) => value is null;
-
     public override void WriteData(WriteContext context, in double[] value, 
bool hasGenerics)
     {
         _ = hasGenerics;
diff --git a/csharp/src/Fory/PrimitiveCollectionSerializers.cs 
b/csharp/src/Fory/PrimitiveCollectionSerializers.cs
index 17850477a..e029816fb 100644
--- a/csharp/src/Fory/PrimitiveCollectionSerializers.cs
+++ b/csharp/src/Fory/PrimitiveCollectionSerializers.cs
@@ -53,16 +53,11 @@ internal sealed class ListBoolSerializer : 
Serializer<List<bool>>
 {
     private static readonly ListSerializer<bool> Fallback = new();
 
-    public override TypeId StaticTypeId => TypeId.List;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override List<bool> DefaultValue => null!;
 
-    public override bool IsNone(in List<bool> value) => value is null;
-
     public override void WriteData(WriteContext context, in List<bool> value, 
bool hasGenerics)
     {
         List<bool> list = value ?? [];
@@ -83,16 +78,11 @@ internal sealed class ListInt8Serializer : 
Serializer<List<sbyte>>
 {
     private static readonly ListSerializer<sbyte> Fallback = new();
 
-    public override TypeId StaticTypeId => TypeId.List;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override List<sbyte> DefaultValue => null!;
 
-    public override bool IsNone(in List<sbyte> value) => value is null;
-
     public override void WriteData(WriteContext context, in List<sbyte> value, 
bool hasGenerics)
     {
         List<sbyte> list = value ?? [];
@@ -113,16 +103,11 @@ internal sealed class ListInt16Serializer : 
Serializer<List<short>>
 {
     private static readonly ListSerializer<short> Fallback = new();
 
-    public override TypeId StaticTypeId => TypeId.List;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override List<short> DefaultValue => null!;
 
-    public override bool IsNone(in List<short> value) => value is null;
-
     public override void WriteData(WriteContext context, in List<short> value, 
bool hasGenerics)
     {
         List<short> list = value ?? [];
@@ -143,16 +128,11 @@ internal sealed class ListIntSerializer : 
Serializer<List<int>>
 {
     private static readonly ListSerializer<int> Fallback = new();
 
-    public override TypeId StaticTypeId => TypeId.List;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override List<int> DefaultValue => null!;
 
-    public override bool IsNone(in List<int> value) => value is null;
-
     public override void WriteData(WriteContext context, in List<int> value, 
bool hasGenerics)
     {
         List<int> list = value ?? [];
@@ -173,16 +153,11 @@ internal sealed class ListLongSerializer : 
Serializer<List<long>>
 {
     private static readonly ListSerializer<long> Fallback = new();
 
-    public override TypeId StaticTypeId => TypeId.List;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override List<long> DefaultValue => null!;
 
-    public override bool IsNone(in List<long> value) => value is null;
-
     public override void WriteData(WriteContext context, in List<long> value, 
bool hasGenerics)
     {
         List<long> list = value ?? [];
@@ -203,16 +178,11 @@ internal sealed class ListUInt8Serializer : 
Serializer<List<byte>>
 {
     private static readonly ListSerializer<byte> Fallback = new();
 
-    public override TypeId StaticTypeId => TypeId.List;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override List<byte> DefaultValue => null!;
 
-    public override bool IsNone(in List<byte> value) => value is null;
-
     public override void WriteData(WriteContext context, in List<byte> value, 
bool hasGenerics)
     {
         List<byte> list = value ?? [];
@@ -233,16 +203,11 @@ internal sealed class ListUInt16Serializer : 
Serializer<List<ushort>>
 {
     private static readonly ListSerializer<ushort> Fallback = new();
 
-    public override TypeId StaticTypeId => TypeId.List;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override List<ushort> DefaultValue => null!;
 
-    public override bool IsNone(in List<ushort> value) => value is null;
-
     public override void WriteData(WriteContext context, in List<ushort> 
value, bool hasGenerics)
     {
         List<ushort> list = value ?? [];
@@ -263,16 +228,11 @@ internal sealed class ListUIntSerializer : 
Serializer<List<uint>>
 {
     private static readonly ListSerializer<uint> Fallback = new();
 
-    public override TypeId StaticTypeId => TypeId.List;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override List<uint> DefaultValue => null!;
 
-    public override bool IsNone(in List<uint> value) => value is null;
-
     public override void WriteData(WriteContext context, in List<uint> value, 
bool hasGenerics)
     {
         List<uint> list = value ?? [];
@@ -293,16 +253,11 @@ internal sealed class ListULongSerializer : 
Serializer<List<ulong>>
 {
     private static readonly ListSerializer<ulong> Fallback = new();
 
-    public override TypeId StaticTypeId => TypeId.List;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override List<ulong> DefaultValue => null!;
 
-    public override bool IsNone(in List<ulong> value) => value is null;
-
     public override void WriteData(WriteContext context, in List<ulong> value, 
bool hasGenerics)
     {
         List<ulong> list = value ?? [];
@@ -323,16 +278,11 @@ internal sealed class ListFloatSerializer : 
Serializer<List<float>>
 {
     private static readonly ListSerializer<float> Fallback = new();
 
-    public override TypeId StaticTypeId => TypeId.List;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override List<float> DefaultValue => null!;
 
-    public override bool IsNone(in List<float> value) => value is null;
-
     public override void WriteData(WriteContext context, in List<float> value, 
bool hasGenerics)
     {
         List<float> list = value ?? [];
@@ -353,16 +303,11 @@ internal sealed class ListDoubleSerializer : 
Serializer<List<double>>
 {
     private static readonly ListSerializer<double> Fallback = new();
 
-    public override TypeId StaticTypeId => TypeId.List;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override List<double> DefaultValue => null!;
 
-    public override bool IsNone(in List<double> value) => value is null;
-
     public override void WriteData(WriteContext context, in List<double> 
value, bool hasGenerics)
     {
         List<double> list = value ?? [];
@@ -383,16 +328,11 @@ internal sealed class ListStringSerializer : 
Serializer<List<string>>
 {
     private static readonly ListSerializer<string> Fallback = new();
 
-    public override TypeId StaticTypeId => TypeId.List;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override List<string> DefaultValue => null!;
 
-    public override bool IsNone(in List<string> value) => value is null;
-
     public override void WriteData(WriteContext context, in List<string> 
value, bool hasGenerics)
     {
         List<string> list = value ?? [];
@@ -441,16 +381,11 @@ internal sealed class SetInt8Serializer : 
Serializer<HashSet<sbyte>>
 {
     private static readonly SetSerializer<sbyte> Fallback = new();
 
-    public override TypeId StaticTypeId => TypeId.Set;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override HashSet<sbyte> DefaultValue => null!;
 
-    public override bool IsNone(in HashSet<sbyte> value) => value is null;
-
     public override void WriteData(WriteContext context, in HashSet<sbyte> 
value, bool hasGenerics)
     {
         HashSet<sbyte> set = value ?? [];
@@ -471,16 +406,11 @@ internal sealed class SetInt16Serializer : 
Serializer<HashSet<short>>
 {
     private static readonly SetSerializer<short> Fallback = new();
 
-    public override TypeId StaticTypeId => TypeId.Set;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override HashSet<short> DefaultValue => null!;
 
-    public override bool IsNone(in HashSet<short> value) => value is null;
-
     public override void WriteData(WriteContext context, in HashSet<short> 
value, bool hasGenerics)
     {
         HashSet<short> set = value ?? [];
@@ -501,16 +431,11 @@ internal sealed class SetIntSerializer : 
Serializer<HashSet<int>>
 {
     private static readonly SetSerializer<int> Fallback = new();
 
-    public override TypeId StaticTypeId => TypeId.Set;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override HashSet<int> DefaultValue => null!;
 
-    public override bool IsNone(in HashSet<int> value) => value is null;
-
     public override void WriteData(WriteContext context, in HashSet<int> 
value, bool hasGenerics)
     {
         HashSet<int> set = value ?? [];
@@ -531,16 +456,11 @@ internal sealed class SetLongSerializer : 
Serializer<HashSet<long>>
 {
     private static readonly SetSerializer<long> Fallback = new();
 
-    public override TypeId StaticTypeId => TypeId.Set;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override HashSet<long> DefaultValue => null!;
 
-    public override bool IsNone(in HashSet<long> value) => value is null;
-
     public override void WriteData(WriteContext context, in HashSet<long> 
value, bool hasGenerics)
     {
         HashSet<long> set = value ?? [];
@@ -561,16 +481,11 @@ internal sealed class SetUInt8Serializer : 
Serializer<HashSet<byte>>
 {
     private static readonly SetSerializer<byte> Fallback = new();
 
-    public override TypeId StaticTypeId => TypeId.Set;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override HashSet<byte> DefaultValue => null!;
 
-    public override bool IsNone(in HashSet<byte> value) => value is null;
-
     public override void WriteData(WriteContext context, in HashSet<byte> 
value, bool hasGenerics)
     {
         HashSet<byte> set = value ?? [];
@@ -591,16 +506,11 @@ internal sealed class SetUInt16Serializer : 
Serializer<HashSet<ushort>>
 {
     private static readonly SetSerializer<ushort> Fallback = new();
 
-    public override TypeId StaticTypeId => TypeId.Set;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override HashSet<ushort> DefaultValue => null!;
 
-    public override bool IsNone(in HashSet<ushort> value) => value is null;
-
     public override void WriteData(WriteContext context, in HashSet<ushort> 
value, bool hasGenerics)
     {
         HashSet<ushort> set = value ?? [];
@@ -621,16 +531,11 @@ internal sealed class SetUIntSerializer : 
Serializer<HashSet<uint>>
 {
     private static readonly SetSerializer<uint> Fallback = new();
 
-    public override TypeId StaticTypeId => TypeId.Set;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override HashSet<uint> DefaultValue => null!;
 
-    public override bool IsNone(in HashSet<uint> value) => value is null;
-
     public override void WriteData(WriteContext context, in HashSet<uint> 
value, bool hasGenerics)
     {
         HashSet<uint> set = value ?? [];
@@ -651,16 +556,11 @@ internal sealed class SetULongSerializer : 
Serializer<HashSet<ulong>>
 {
     private static readonly SetSerializer<ulong> Fallback = new();
 
-    public override TypeId StaticTypeId => TypeId.Set;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override HashSet<ulong> DefaultValue => null!;
 
-    public override bool IsNone(in HashSet<ulong> value) => value is null;
-
     public override void WriteData(WriteContext context, in HashSet<ulong> 
value, bool hasGenerics)
     {
         HashSet<ulong> set = value ?? [];
@@ -681,16 +581,11 @@ internal sealed class SetFloatSerializer : 
Serializer<HashSet<float>>
 {
     private static readonly SetSerializer<float> Fallback = new();
 
-    public override TypeId StaticTypeId => TypeId.Set;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override HashSet<float> DefaultValue => null!;
 
-    public override bool IsNone(in HashSet<float> value) => value is null;
-
     public override void WriteData(WriteContext context, in HashSet<float> 
value, bool hasGenerics)
     {
         HashSet<float> set = value ?? [];
@@ -711,16 +606,11 @@ internal sealed class SetDoubleSerializer : 
Serializer<HashSet<double>>
 {
     private static readonly SetSerializer<double> Fallback = new();
 
-    public override TypeId StaticTypeId => TypeId.Set;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override HashSet<double> DefaultValue => null!;
 
-    public override bool IsNone(in HashSet<double> value) => value is null;
-
     public override void WriteData(WriteContext context, in HashSet<double> 
value, bool hasGenerics)
     {
         HashSet<double> set = value ?? [];
@@ -742,16 +632,11 @@ internal class PrimitiveLinkedListSerializer<T, TCodec> : 
Serializer<LinkedList<
 {
     private static readonly LinkedListSerializer<T> Fallback = new();
 
-    public override TypeId StaticTypeId => TypeId.List;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override LinkedList<T> DefaultValue => null!;
 
-    public override bool IsNone(in LinkedList<T> value) => value is null;
-
     public override void WriteData(WriteContext context, in LinkedList<T> 
value, bool hasGenerics)
     {
         if (TCodec.IsNullable)
@@ -778,16 +663,11 @@ internal class PrimitiveQueueSerializer<T, TCodec> : 
Serializer<Queue<T>>
 {
     private static readonly QueueSerializer<T> Fallback = new();
 
-    public override TypeId StaticTypeId => TypeId.List;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override Queue<T> DefaultValue => null!;
 
-    public override bool IsNone(in Queue<T> value) => value is null;
-
     public override void WriteData(WriteContext context, in Queue<T> value, 
bool hasGenerics)
     {
         if (TCodec.IsNullable)
@@ -814,16 +694,11 @@ internal class PrimitiveStackSerializer<T, TCodec> : 
Serializer<Stack<T>>
 {
     private static readonly StackSerializer<T> Fallback = new();
 
-    public override TypeId StaticTypeId => TypeId.List;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override Stack<T> DefaultValue => null!;
 
-    public override bool IsNone(in Stack<T> value) => value is null;
-
     public override void WriteData(WriteContext context, in Stack<T> value, 
bool hasGenerics)
     {
         if (TCodec.IsNullable)
@@ -857,16 +732,11 @@ internal class PrimitiveSortedSetSerializer<T, TCodec> : 
Serializer<SortedSet<T>
 {
     private static readonly SortedSetSerializer<T> Fallback = new();
 
-    public override TypeId StaticTypeId => TypeId.Set;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override SortedSet<T> DefaultValue => null!;
 
-    public override bool IsNone(in SortedSet<T> value) => value is null;
-
     public override void WriteData(WriteContext context, in SortedSet<T> 
value, bool hasGenerics)
     {
         if (TCodec.IsNullable)
@@ -894,16 +764,11 @@ internal class PrimitiveImmutableHashSetSerializer<T, 
TCodec> : Serializer<Immut
 {
     private static readonly ImmutableHashSetSerializer<T> Fallback = new();
 
-    public override TypeId StaticTypeId => TypeId.Set;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override ImmutableHashSet<T> DefaultValue => null!;
 
-    public override bool IsNone(in ImmutableHashSet<T> value) => value is null;
-
     public override void WriteData(WriteContext context, in 
ImmutableHashSet<T> value, bool hasGenerics)
     {
         if (TCodec.IsNullable)
diff --git a/csharp/src/Fory/PrimitiveDictionarySerializers.cs 
b/csharp/src/Fory/PrimitiveDictionarySerializers.cs
index cfc1a1913..3e024b92e 100644
--- a/csharp/src/Fory/PrimitiveDictionarySerializers.cs
+++ b/csharp/src/Fory/PrimitiveDictionarySerializers.cs
@@ -732,16 +732,11 @@ internal class PrimitiveDictionarySerializer<TKey, 
TValue, TKeyCodec, TValueCode
     where TKeyCodec : struct, IPrimitiveDictionaryCodec<TKey>
     where TValueCodec : struct, IPrimitiveDictionaryCodec<TValue>
 {
-    public override TypeId StaticTypeId => TypeId.Map;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override Dictionary<TKey, TValue> DefaultValue => null!;
 
-    public override bool IsNone(in Dictionary<TKey, TValue> value) => value is 
null;
-
 public override void WriteData(WriteContext context, in Dictionary<TKey, 
TValue> value, bool hasGenerics)
     {
         Dictionary<TKey, TValue> map = value ?? [];
@@ -785,16 +780,11 @@ internal class PrimitiveSortedDictionarySerializer<TKey, 
TValue, TKeyCodec, TVal
     where TKeyCodec : struct, IPrimitiveDictionaryCodec<TKey>
     where TValueCodec : struct, IPrimitiveDictionaryCodec<TValue>
 {
-    public override TypeId StaticTypeId => TypeId.Map;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override SortedDictionary<TKey, TValue> DefaultValue => null!;
 
-    public override bool IsNone(in SortedDictionary<TKey, TValue> value) => 
value is null;
-
     public override void WriteData(WriteContext context, in 
SortedDictionary<TKey, TValue> value, bool hasGenerics)
     {
         SortedDictionary<TKey, TValue> map = value ?? new 
SortedDictionary<TKey, TValue>();
@@ -838,16 +828,11 @@ internal class PrimitiveSortedListSerializer<TKey, 
TValue, TKeyCodec, TValueCode
     where TKeyCodec : struct, IPrimitiveDictionaryCodec<TKey>
     where TValueCodec : struct, IPrimitiveDictionaryCodec<TValue>
 {
-    public override TypeId StaticTypeId => TypeId.Map;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override SortedList<TKey, TValue> DefaultValue => null!;
 
-    public override bool IsNone(in SortedList<TKey, TValue> value) => value is 
null;
-
     public override void WriteData(WriteContext context, in SortedList<TKey, 
TValue> value, bool hasGenerics)
     {
         SortedList<TKey, TValue> map = value ?? new SortedList<TKey, TValue>();
@@ -891,16 +876,11 @@ internal class 
PrimitiveConcurrentDictionarySerializer<TKey, TValue, TKeyCodec,
     where TKeyCodec : struct, IPrimitiveDictionaryCodec<TKey>
     where TValueCodec : struct, IPrimitiveDictionaryCodec<TValue>
 {
-    public override TypeId StaticTypeId => TypeId.Map;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override ConcurrentDictionary<TKey, TValue> DefaultValue => null!;
 
-    public override bool IsNone(in ConcurrentDictionary<TKey, TValue> value) 
=> value is null;
-
     public override void WriteData(WriteContext context, in 
ConcurrentDictionary<TKey, TValue> value, bool hasGenerics)
     {
         ConcurrentDictionary<TKey, TValue> map = value ?? new 
ConcurrentDictionary<TKey, TValue>();
diff --git a/csharp/src/Fory/PrimitiveSerializers.cs 
b/csharp/src/Fory/PrimitiveSerializers.cs
index 98d85dcca..ad5e3e959 100644
--- a/csharp/src/Fory/PrimitiveSerializers.cs
+++ b/csharp/src/Fory/PrimitiveSerializers.cs
@@ -26,7 +26,6 @@ internal enum ForyStringEncoding : ulong
 
 public sealed class BoolSerializer : Serializer<bool>
 {
-    public override TypeId StaticTypeId => TypeId.Bool;
 
     public override bool DefaultValue => false;
 
@@ -44,7 +43,6 @@ public sealed class BoolSerializer : Serializer<bool>
 
 public sealed class Int8Serializer : Serializer<sbyte>
 {
-    public override TypeId StaticTypeId => TypeId.Int8;
 
     public override sbyte DefaultValue => 0;
 
@@ -62,7 +60,6 @@ public sealed class Int8Serializer : Serializer<sbyte>
 
 public sealed class Int16Serializer : Serializer<short>
 {
-    public override TypeId StaticTypeId => TypeId.Int16;
 
     public override short DefaultValue => 0;
 
@@ -80,7 +77,6 @@ public sealed class Int16Serializer : Serializer<short>
 
 public sealed class Int32Serializer : Serializer<int>
 {
-    public override TypeId StaticTypeId => TypeId.VarInt32;
 
     public override int DefaultValue => 0;
 
@@ -98,7 +94,6 @@ public sealed class Int32Serializer : Serializer<int>
 
 public sealed class Int64Serializer : Serializer<long>
 {
-    public override TypeId StaticTypeId => TypeId.VarInt64;
 
     public override long DefaultValue => 0;
 
@@ -116,7 +111,6 @@ public sealed class Int64Serializer : Serializer<long>
 
 public sealed class UInt8Serializer : Serializer<byte>
 {
-    public override TypeId StaticTypeId => TypeId.UInt8;
 
     public override byte DefaultValue => 0;
 
@@ -134,7 +128,6 @@ public sealed class UInt8Serializer : Serializer<byte>
 
 public sealed class UInt16Serializer : Serializer<ushort>
 {
-    public override TypeId StaticTypeId => TypeId.UInt16;
 
     public override ushort DefaultValue => 0;
 
@@ -152,7 +145,6 @@ public sealed class UInt16Serializer : Serializer<ushort>
 
 public sealed class UInt32Serializer : Serializer<uint>
 {
-    public override TypeId StaticTypeId => TypeId.VarUInt32;
 
     public override uint DefaultValue => 0;
 
@@ -170,7 +162,6 @@ public sealed class UInt32Serializer : Serializer<uint>
 
 public sealed class UInt64Serializer : Serializer<ulong>
 {
-    public override TypeId StaticTypeId => TypeId.VarUInt64;
 
     public override ulong DefaultValue => 0;
 
@@ -188,7 +179,6 @@ public sealed class UInt64Serializer : Serializer<ulong>
 
 public sealed class Float32Serializer : Serializer<float>
 {
-    public override TypeId StaticTypeId => TypeId.Float32;
 
     public override float DefaultValue => 0;
 
@@ -206,7 +196,6 @@ public sealed class Float32Serializer : Serializer<float>
 
 public sealed class Float64Serializer : Serializer<double>
 {
-    public override TypeId StaticTypeId => TypeId.Float64;
 
     public override double DefaultValue => 0;
 
@@ -224,14 +213,10 @@ public sealed class Float64Serializer : Serializer<double>
 
 public sealed class BinarySerializer : Serializer<byte[]>
 {
-    public override TypeId StaticTypeId => TypeId.Binary;
 
-    public override bool IsNullableType => true;
 
     public override byte[] DefaultValue => null!;
 
-    public override bool IsNone(in byte[] value) => value is null;
-
     public override void WriteData(WriteContext context, in byte[] value, bool 
hasGenerics)
     {
         _ = hasGenerics;
diff --git a/csharp/src/Fory/Serializer.cs b/csharp/src/Fory/Serializer.cs
index 009014cfb..a90242a3e 100644
--- a/csharp/src/Fory/Serializer.cs
+++ b/csharp/src/Fory/Serializer.cs
@@ -17,56 +17,11 @@
 
 namespace Apache.Fory;
 
-public abstract class Serializer
+public abstract class Serializer<T>
 {
-    public abstract Type Type { get; }
-
-    public abstract TypeId StaticTypeId { get; }
-
-    public abstract bool IsNullableType { get; }
-
-    public abstract bool IsReferenceTrackableType { get; }
-
-    public abstract object? DefaultObject { get; }
-
-    public abstract bool IsNoneObject(object? value);
-
-    public abstract void WriteDataObject(WriteContext context, object? value, 
bool hasGenerics);
-
-    public abstract object? ReadDataObject(ReadContext context);
-
-    public abstract void WriteObject(WriteContext context, object? value, 
RefMode refMode, bool writeTypeInfo, bool hasGenerics);
-
-    public abstract object? ReadObject(ReadContext context, RefMode refMode, 
bool readTypeInfo);
-
-    public abstract void WriteTypeInfo(WriteContext context);
-
-    public abstract void ReadTypeInfo(ReadContext context);
-
-    public abstract IReadOnlyList<TypeMetaFieldInfo> 
CompatibleTypeMetaFields(bool trackRef);
-
-    public abstract Serializer<T> RequireSerializer<T>();
-}
-
-public abstract class Serializer<T> : Serializer
-{
-    public override Type Type => typeof(T);
-
-    public abstract override TypeId StaticTypeId { get; }
-
-    public override bool IsNullableType => false;
-
-    public override bool IsReferenceTrackableType => false;
-
     public virtual T DefaultValue => default!;
 
-    public override object? DefaultObject => DefaultValue;
-
-    public virtual bool IsNone(in T value)
-    {
-        _ = value;
-        return false;
-    }
+    internal object? DefaultObject => DefaultValue;
 
     public abstract void WriteData(WriteContext context, in T value, bool 
hasGenerics);
 
@@ -78,7 +33,6 @@ public abstract class Serializer<T> : Serializer
         {
             bool wroteTrackingRefFlag = false;
             if (refMode == RefMode.Tracking &&
-                IsReferenceTrackableType &&
                 value is object obj)
             {
                 if (context.RefWriter.TryWriteReference(context.Writer, obj))
@@ -91,7 +45,7 @@ public abstract class Serializer<T> : Serializer
 
             if (!wroteTrackingRefFlag)
             {
-                if (IsNullableType && IsNone(value))
+                if (value is null)
                 {
                     context.Writer.WriteInt8((sbyte)RefFlag.Null);
                     return;
@@ -103,7 +57,7 @@ public abstract class Serializer<T> : Serializer
 
         if (writeTypeInfo)
         {
-            WriteTypeInfo(context);
+            context.TypeResolver.WriteTypeInfo(this, context);
         }
 
         WriteData(context, value, hasGenerics);
@@ -130,7 +84,7 @@ public abstract class Serializer<T> : Serializer
                     context.RefReader.PushPendingReference(reservedRefId);
                     if (readTypeInfo)
                     {
-                        ReadTypeInfo(context);
+                        context.TypeResolver.ReadTypeInfo(this, context);
                     }
 
                     T value = ReadData(context);
@@ -147,81 +101,9 @@ public abstract class Serializer<T> : Serializer
 
         if (readTypeInfo)
         {
-            ReadTypeInfo(context);
-        }
-
-        return ReadData(context);
-    }
-
-    public override void WriteTypeInfo(WriteContext context)
-    {
-        context.TypeResolver.WriteTypeInfo(Type, this, context);
-    }
-
-    public override void ReadTypeInfo(ReadContext context)
-    {
-        context.TypeResolver.ReadTypeInfo(Type, this, context);
-    }
-
-    public override IReadOnlyList<TypeMetaFieldInfo> 
CompatibleTypeMetaFields(bool trackRef)
-    {
-        _ = trackRef;
-        return [];
-    }
-
-    public override bool IsNoneObject(object? value)
-    {
-        if (value is null)
-        {
-            return IsNullableType;
+            context.TypeResolver.ReadTypeInfo(this, context);
         }
 
-        return value is T typed && IsNone(typed);
-    }
-
-    public override void WriteDataObject(WriteContext context, object? value, 
bool hasGenerics)
-    {
-        WriteData(context, CoerceValue(value), hasGenerics);
-    }
-
-    public override object? ReadDataObject(ReadContext context)
-    {
         return ReadData(context);
     }
-
-    public override void WriteObject(WriteContext context, object? value, 
RefMode refMode, bool writeTypeInfo, bool hasGenerics)
-    {
-        Write(context, CoerceValue(value), refMode, writeTypeInfo, 
hasGenerics);
-    }
-
-    public override object? ReadObject(ReadContext context, RefMode refMode, 
bool readTypeInfo)
-    {
-        return Read(context, refMode, readTypeInfo);
-    }
-
-    public override Serializer<TCast> RequireSerializer<TCast>()
-    {
-        if (typeof(TCast) == typeof(T))
-        {
-            return (Serializer<TCast>)(object)this;
-        }
-
-        throw new InvalidDataException($"serializer type mismatch for 
{typeof(TCast)}");
-    }
-
-    protected virtual T CoerceValue(object? value)
-    {
-        if (value is T typed)
-        {
-            return typed;
-        }
-
-        if (value is null && IsNullableType)
-        {
-            return DefaultValue;
-        }
-
-        throw new InvalidDataException(
-            $"serializer {GetType().Name} expected value of type {typeof(T)}, 
got {value?.GetType()}");
-    }
 }
diff --git a/csharp/src/Fory/StringSerializer.cs 
b/csharp/src/Fory/StringSerializer.cs
index 9dcf4ff5a..1e56fcfd6 100644
--- a/csharp/src/Fory/StringSerializer.cs
+++ b/csharp/src/Fory/StringSerializer.cs
@@ -21,14 +21,10 @@ namespace Apache.Fory;
 
 public sealed class StringSerializer : Serializer<string>
 {
-    public override TypeId StaticTypeId => TypeId.String;
 
-    public override bool IsNullableType => true;
 
     public override string DefaultValue => null!;
 
-    public override bool IsNone(in string value) => value is null;
-
     public override void WriteData(WriteContext context, in string value, bool 
hasGenerics)
     {
         _ = hasGenerics;
diff --git a/csharp/src/Fory/TimeSerializers.cs 
b/csharp/src/Fory/TimeSerializers.cs
index acaf0e99f..24c6825c1 100644
--- a/csharp/src/Fory/TimeSerializers.cs
+++ b/csharp/src/Fory/TimeSerializers.cs
@@ -89,7 +89,6 @@ internal static class TimeCodec
 
 public sealed class DateOnlySerializer : Serializer<DateOnly>
 {
-    public override TypeId StaticTypeId => TypeId.Date;
 
     public override DateOnly DefaultValue => new(1970, 1, 1);
 
@@ -107,7 +106,6 @@ public sealed class DateOnlySerializer : 
Serializer<DateOnly>
 
 public sealed class DateTimeOffsetSerializer : Serializer<DateTimeOffset>
 {
-    public override TypeId StaticTypeId => TypeId.Timestamp;
 
     public override DateTimeOffset DefaultValue => DateTimeOffset.UnixEpoch;
 
@@ -125,7 +123,6 @@ public sealed class DateTimeOffsetSerializer : 
Serializer<DateTimeOffset>
 
 public sealed class DateTimeSerializer : Serializer<DateTime>
 {
-    public override TypeId StaticTypeId => TypeId.Timestamp;
 
     public override DateTime DefaultValue => DateTime.UnixEpoch;
 
@@ -144,7 +141,6 @@ public sealed class DateTimeSerializer : 
Serializer<DateTime>
 
 public sealed class TimeSpanSerializer : Serializer<TimeSpan>
 {
-    public override TypeId StaticTypeId => TypeId.Duration;
 
     public override TimeSpan DefaultValue => TimeSpan.Zero;
 
@@ -164,16 +160,11 @@ internal sealed class ListDateOnlySerializer : 
Serializer<List<DateOnly>>
 {
     private static readonly ListSerializer<DateOnly> Fallback = new();
 
-    public override TypeId StaticTypeId => TypeId.List;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override List<DateOnly> DefaultValue => null!;
 
-    public override bool IsNone(in List<DateOnly> value) => value is null;
-
     public override void WriteData(WriteContext context, in List<DateOnly> 
value, bool hasGenerics)
     {
         List<DateOnly> list = value ?? [];
@@ -194,16 +185,11 @@ internal sealed class ListDateTimeOffsetSerializer : 
Serializer<List<DateTimeOff
 {
     private static readonly ListSerializer<DateTimeOffset> Fallback = new();
 
-    public override TypeId StaticTypeId => TypeId.List;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override List<DateTimeOffset> DefaultValue => null!;
 
-    public override bool IsNone(in List<DateTimeOffset> value) => value is 
null;
-
     public override void WriteData(WriteContext context, in 
List<DateTimeOffset> value, bool hasGenerics)
     {
         List<DateTimeOffset> list = value ?? [];
@@ -224,16 +210,11 @@ internal sealed class ListDateTimeSerializer : 
Serializer<List<DateTime>>
 {
     private static readonly ListSerializer<DateTime> Fallback = new();
 
-    public override TypeId StaticTypeId => TypeId.List;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override List<DateTime> DefaultValue => null!;
 
-    public override bool IsNone(in List<DateTime> value) => value is null;
-
     public override void WriteData(WriteContext context, in List<DateTime> 
value, bool hasGenerics)
     {
         List<DateTime> list = value ?? [];
@@ -255,16 +236,11 @@ internal sealed class ListTimeSpanSerializer : 
Serializer<List<TimeSpan>>
 {
     private static readonly ListSerializer<TimeSpan> Fallback = new();
 
-    public override TypeId StaticTypeId => TypeId.List;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override List<TimeSpan> DefaultValue => null!;
 
-    public override bool IsNone(in List<TimeSpan> value) => value is null;
-
     public override void WriteData(WriteContext context, in List<TimeSpan> 
value, bool hasGenerics)
     {
         List<TimeSpan> list = value ?? [];
diff --git a/csharp/src/Fory/TypeId.cs b/csharp/src/Fory/TypeId.cs
index 47857e091..da6d94cff 100644
--- a/csharp/src/Fory/TypeId.cs
+++ b/csharp/src/Fory/TypeId.cs
@@ -80,24 +80,6 @@ public enum TypeId : uint
 
 internal static class TypeIdExtensions
 {
-    public static bool IsUserTypeKind(this TypeId typeId)
-    {
-        return typeId switch
-        {
-            TypeId.Enum or
-                TypeId.NamedEnum or
-                TypeId.Struct or
-                TypeId.CompatibleStruct or
-                TypeId.NamedStruct or
-                TypeId.NamedCompatibleStruct or
-                TypeId.Ext or
-                TypeId.NamedExt or
-                TypeId.TypedUnion or
-                TypeId.NamedUnion => true,
-            _ => false,
-        };
-    }
-
     public static bool NeedsTypeInfoForField(this TypeId typeId)
     {
         return typeId switch
@@ -113,4 +95,3 @@ internal static class TypeIdExtensions
         };
     }
 }
-
diff --git a/csharp/src/Fory/TypeInfo.cs b/csharp/src/Fory/TypeInfo.cs
index 270fa1e20..f6262b22e 100644
--- a/csharp/src/Fory/TypeInfo.cs
+++ b/csharp/src/Fory/TypeInfo.cs
@@ -15,32 +15,464 @@
 // specific language governing permissions and limitations
 // under the License.
 
+using System.Collections.Concurrent;
+using System.Collections.Immutable;
+using System.Reflection;
+
 namespace Apache.Fory;
 
+internal enum UserTypeKind
+{
+    Enum,
+    Struct,
+    Ext,
+    TypedUnion,
+}
+
 public sealed class TypeInfo
 {
-    internal TypeInfo(Type type, Serializer serializer)
+    private readonly object _serializer;
+    private readonly Action<WriteContext, object?, bool> _writeDataObject;
+    private readonly Func<ReadContext, object?> _readDataObject;
+    private readonly Action<WriteContext, object?, RefMode, bool, bool> 
_writeObject;
+    private readonly Func<ReadContext, RefMode, bool, object?> _readObject;
+    private readonly Func<bool, IReadOnlyList<TypeMetaFieldInfo>> 
_compatibleTypeMetaFields;
+    private static readonly IReadOnlyList<TypeMetaFieldInfo> 
EmptyTypeMetaFields = Array.Empty<TypeMetaFieldInfo>();
+
+    private TypeInfo(
+        Type type,
+        object serializer,
+        TypeId? builtInTypeId,
+        UserTypeKind? userTypeKind,
+        bool isDynamicType,
+        bool isNullableType,
+        bool isReferenceTrackableType,
+        object? defaultObject,
+        Action<WriteContext, object?, bool> writeDataObject,
+        Func<ReadContext, object?> readDataObject,
+        Action<WriteContext, object?, RefMode, bool, bool> writeObject,
+        Func<ReadContext, RefMode, bool, object?> readObject,
+        Func<bool, IReadOnlyList<TypeMetaFieldInfo>> compatibleTypeMetaFields)
     {
         Type = type;
-        Serializer = serializer;
-        StaticTypeId = serializer.StaticTypeId;
-        IsNullableType = serializer.IsNullableType;
-        IsReferenceTrackableType = serializer.IsReferenceTrackableType;
+        _serializer = serializer;
+        BuiltInTypeId = builtInTypeId;
+        UserTypeKind = userTypeKind;
+        IsDynamicType = isDynamicType;
+        IsNullableType = isNullableType;
+        IsReferenceTrackableType = isReferenceTrackableType;
+        DefaultObject = defaultObject;
+        _writeDataObject = writeDataObject;
+        _readDataObject = readDataObject;
+        _writeObject = writeObject;
+        _readObject = readObject;
+        _compatibleTypeMetaFields = compatibleTypeMetaFields;
+    }
+
+    internal static TypeInfo Create<T>(Type type, Serializer<T> serializer)
+    {
+        Func<bool, IReadOnlyList<TypeMetaFieldInfo>> compatibleTypeMetaFields =
+            CreateCompatibleTypeMetaFieldsProvider(serializer, out bool 
hasCompatibleTypeMetaFieldsProvider);
+        (TypeId? builtInTypeId, UserTypeKind? userTypeKind, bool 
isDynamicType) = ResolveTypeShape(
+            type,
+            hasCompatibleTypeMetaFieldsProvider);
+        bool isNullableType = !type.IsValueType || 
Nullable.GetUnderlyingType(type) is not null;
+        bool isReferenceTrackableType = type != typeof(string) && 
!type.IsValueType;
+        return new TypeInfo(
+            type,
+            serializer,
+            builtInTypeId,
+            userTypeKind,
+            isDynamicType,
+            isNullableType,
+            isReferenceTrackableType,
+            serializer.DefaultObject,
+            (context, value, hasGenerics) => WriteDataObject(serializer, 
context, value, hasGenerics),
+            context => serializer.ReadData(context),
+            (context, value, refMode, writeTypeInfo, hasGenerics) =>
+                WriteObject(serializer, context, value, refMode, 
writeTypeInfo, hasGenerics),
+            (context, refMode, readTypeInfo) => serializer.Read(context, 
refMode, readTypeInfo),
+            compatibleTypeMetaFields);
+    }
+
+    private static Func<bool, IReadOnlyList<TypeMetaFieldInfo>> 
CreateCompatibleTypeMetaFieldsProvider(
+        object serializer,
+        out bool hasProvider)
+    {
+        MethodInfo? method = serializer.GetType().GetMethod(
+            "CompatibleTypeMetaFields",
+            BindingFlags.Instance | BindingFlags.Public | 
BindingFlags.NonPublic,
+            null,
+            [typeof(bool)],
+            null);
+        if (method is null || method.ReturnType != 
typeof(IReadOnlyList<TypeMetaFieldInfo>))
+        {
+            hasProvider = false;
+            return EmptyCompatibleTypeMetaFields;
+        }
+
+        try
+        {
+            Delegate del = method.CreateDelegate(typeof(Func<bool, 
IReadOnlyList<TypeMetaFieldInfo>>), serializer);
+            hasProvider = true;
+            return (Func<bool, IReadOnlyList<TypeMetaFieldInfo>>)del;
+        }
+        catch
+        {
+            hasProvider = false;
+            return EmptyCompatibleTypeMetaFields;
+        }
+    }
+
+    private static IReadOnlyList<TypeMetaFieldInfo> 
EmptyCompatibleTypeMetaFields(bool _)
+    {
+        return EmptyTypeMetaFields;
+    }
+
+    private static void WriteDataObject<T>(Serializer<T> serializer, 
WriteContext context, object? value, bool hasGenerics)
+    {
+        serializer.WriteData(context, CoerceRuntimeValue(serializer, value), 
hasGenerics);
+    }
+
+    private static void WriteObject<T>(
+        Serializer<T> serializer,
+        WriteContext context,
+        object? value,
+        RefMode refMode,
+        bool writeTypeInfo,
+        bool hasGenerics)
+    {
+        serializer.Write(context, CoerceRuntimeValue(serializer, value), 
refMode, writeTypeInfo, hasGenerics);
+    }
+
+    private static T CoerceRuntimeValue<T>(Serializer<T> serializer, object? 
value)
+    {
+        if (value is T typed)
+        {
+            return typed;
+        }
+
+        if (value is null && default(T) is null)
+        {
+            return serializer.DefaultValue;
+        }
+
+        throw new InvalidDataException(
+            $"serializer {serializer.GetType().Name} expected value of type 
{typeof(T)}, got {value?.GetType()}");
+    }
+
+    private static (TypeId? BuiltInTypeId, UserTypeKind? UserTypeKind, bool 
IsDynamicType) ResolveTypeShape(
+        Type type,
+        bool hasCompatibleTypeMetaFieldsProvider)
+    {
+        Type? nullableType = Nullable.GetUnderlyingType(type);
+        if (nullableType is not null)
+        {
+            return ResolveTypeShape(nullableType, 
hasCompatibleTypeMetaFieldsProvider);
+        }
+
+        if (TryResolveBuiltInTypeId(type, out TypeId builtInTypeId))
+        {
+            return (builtInTypeId, null, false);
+        }
+
+        if (type == typeof(object))
+        {
+            return (null, null, true);
+        }
+
+        if (type.IsEnum)
+        {
+            return (null, Apache.Fory.UserTypeKind.Enum, false);
+        }
+
+        if (typeof(Union).IsAssignableFrom(type))
+        {
+            return (null, Apache.Fory.UserTypeKind.TypedUnion, false);
+        }
+
+        if (hasCompatibleTypeMetaFieldsProvider)
+        {
+            return (null, Apache.Fory.UserTypeKind.Struct, false);
+        }
+
+        return (null, Apache.Fory.UserTypeKind.Ext, false);
+    }
+
+    private static bool TryResolveBuiltInTypeId(Type type, out TypeId typeId)
+    {
+        if (type == typeof(bool))
+        {
+            typeId = TypeId.Bool;
+            return true;
+        }
+
+        if (type == typeof(sbyte))
+        {
+            typeId = TypeId.Int8;
+            return true;
+        }
+
+        if (type == typeof(short))
+        {
+            typeId = TypeId.Int16;
+            return true;
+        }
+
+        if (type == typeof(int))
+        {
+            typeId = TypeId.VarInt32;
+            return true;
+        }
+
+        if (type == typeof(long))
+        {
+            typeId = TypeId.VarInt64;
+            return true;
+        }
+
+        if (type == typeof(byte))
+        {
+            typeId = TypeId.UInt8;
+            return true;
+        }
+
+        if (type == typeof(ushort))
+        {
+            typeId = TypeId.UInt16;
+            return true;
+        }
+
+        if (type == typeof(uint))
+        {
+            typeId = TypeId.VarUInt32;
+            return true;
+        }
+
+        if (type == typeof(ulong))
+        {
+            typeId = TypeId.VarUInt64;
+            return true;
+        }
+
+        if (type == typeof(float))
+        {
+            typeId = TypeId.Float32;
+            return true;
+        }
+
+        if (type == typeof(double))
+        {
+            typeId = TypeId.Float64;
+            return true;
+        }
+
+        if (type == typeof(string))
+        {
+            typeId = TypeId.String;
+            return true;
+        }
+
+        if (type == typeof(byte[]))
+        {
+            typeId = TypeId.Binary;
+            return true;
+        }
+
+        if (type == typeof(bool[]))
+        {
+            typeId = TypeId.BoolArray;
+            return true;
+        }
+
+        if (type == typeof(sbyte[]))
+        {
+            typeId = TypeId.Int8Array;
+            return true;
+        }
+
+        if (type == typeof(short[]))
+        {
+            typeId = TypeId.Int16Array;
+            return true;
+        }
+
+        if (type == typeof(int[]))
+        {
+            typeId = TypeId.Int32Array;
+            return true;
+        }
+
+        if (type == typeof(long[]))
+        {
+            typeId = TypeId.Int64Array;
+            return true;
+        }
+
+        if (type == typeof(ushort[]))
+        {
+            typeId = TypeId.UInt16Array;
+            return true;
+        }
+
+        if (type == typeof(uint[]))
+        {
+            typeId = TypeId.UInt32Array;
+            return true;
+        }
+
+        if (type == typeof(ulong[]))
+        {
+            typeId = TypeId.UInt64Array;
+            return true;
+        }
+
+        if (type == typeof(float[]))
+        {
+            typeId = TypeId.Float32Array;
+            return true;
+        }
+
+        if (type == typeof(double[]))
+        {
+            typeId = TypeId.Float64Array;
+            return true;
+        }
+
+        if (type == typeof(DateOnly))
+        {
+            typeId = TypeId.Date;
+            return true;
+        }
+
+        if (type == typeof(DateTimeOffset) || type == typeof(DateTime))
+        {
+            typeId = TypeId.Timestamp;
+            return true;
+        }
+
+        if (type == typeof(TimeSpan))
+        {
+            typeId = TypeId.Duration;
+            return true;
+        }
+
+        if (type.IsArray)
+        {
+            typeId = TypeId.List;
+            return true;
+        }
+
+        if (type.IsGenericType)
+        {
+            Type genericType = type.GetGenericTypeDefinition();
+            if (genericType == typeof(List<>) ||
+                genericType == typeof(LinkedList<>) ||
+                genericType == typeof(Queue<>) ||
+                genericType == typeof(Stack<>))
+            {
+                typeId = TypeId.List;
+                return true;
+            }
+
+            if (genericType == typeof(HashSet<>) ||
+                genericType == typeof(SortedSet<>) ||
+                genericType == typeof(ImmutableHashSet<>))
+            {
+                typeId = TypeId.Set;
+                return true;
+            }
+
+            if (genericType == typeof(Dictionary<,>) ||
+                genericType == typeof(SortedDictionary<,>) ||
+                genericType == typeof(SortedList<,>) ||
+                genericType == typeof(ConcurrentDictionary<,>) ||
+                genericType == typeof(NullableKeyDictionary<,>))
+            {
+                typeId = TypeId.Map;
+                return true;
+            }
+
+            if (genericType == typeof(Nullable<>))
+            {
+                Type? underlying = Nullable.GetUnderlyingType(type);
+                if (underlying is not null)
+                {
+                    return TryResolveBuiltInTypeId(underlying, out typeId);
+                }
+            }
+        }
+
+        typeId = default;
+        return false;
     }
 
     public Type Type { get; }
 
-    internal Serializer Serializer { get; }
+    public bool IsBuiltinType => BuiltInTypeId.HasValue;
+
+    public TypeId? BuiltInTypeId { get; }
+
+    public bool IsUserType => UserTypeKind.HasValue;
 
-    public TypeId StaticTypeId { get; }
+    internal UserTypeKind? UserTypeKind { get; }
+
+    public bool IsDynamicType { get; }
 
     public bool IsNullableType { get; }
 
     public bool IsReferenceTrackableType { get; }
 
-    public void WriteObject(WriteContext context, object? value, RefMode 
refMode, bool writeTypeInfo, bool hasGenerics)
+    public object? DefaultObject { get; }
+
+    public bool NeedsTypeInfoForField()
+    {
+        if (IsDynamicType)
+        {
+            return true;
+        }
+
+        if (!UserTypeKind.HasValue)
+        {
+            return false;
+        }
+
+        return UserTypeKind.Value is Apache.Fory.UserTypeKind.Struct or 
Apache.Fory.UserTypeKind.Ext;
+    }
+
+    internal Serializer<T> RequireSerializer<T>()
+    {
+        if (_serializer is Serializer<T> serializer)
+        {
+            return serializer;
+        }
+
+        throw new InvalidDataException($"serializer type mismatch for 
{typeof(T)}");
+    }
+
+    internal void WriteDataObject(WriteContext context, object? value, bool 
hasGenerics)
+    {
+        _writeDataObject(context, value, hasGenerics);
+    }
+
+    internal object? ReadDataObject(ReadContext context)
+    {
+        return _readDataObject(context);
+    }
+
+    internal void WriteObject(WriteContext context, object? value, RefMode 
refMode, bool writeTypeInfo, bool hasGenerics)
+    {
+        _writeObject(context, value, refMode, writeTypeInfo, hasGenerics);
+    }
+
+    internal object? ReadObject(ReadContext context, RefMode refMode, bool 
readTypeInfo)
+    {
+        return _readObject(context, refMode, readTypeInfo);
+    }
+
+    internal IReadOnlyList<TypeMetaFieldInfo> CompatibleTypeMetaFields(bool 
trackRef)
     {
-        Serializer.WriteObject(context, value, refMode, writeTypeInfo, 
hasGenerics);
+        return _compatibleTypeMetaFields(trackRef);
     }
 
     internal bool IsRegistered { get; private set; }
diff --git a/csharp/src/Fory/TypeResolver.cs b/csharp/src/Fory/TypeResolver.cs
index 62fd83c6c..2f8b8ee72 100644
--- a/csharp/src/Fory/TypeResolver.cs
+++ b/csharp/src/Fory/TypeResolver.cs
@@ -17,13 +17,22 @@
 
 using System.Collections.Concurrent;
 using System.Collections.Immutable;
+using System.Reflection;
 
 namespace Apache.Fory;
 
 public sealed class TypeResolver
 {
-    private static readonly ConcurrentDictionary<Type, Func<Serializer>> 
GeneratedFactories = new();
+    private static readonly ConcurrentDictionary<Type, Func<TypeResolver, 
TypeInfo>> GeneratedFactories = new();
     private static readonly ConcurrentDictionary<TypeId, HashSet<TypeId>> 
SingleAllowedWireTypes = new();
+    private static readonly MethodInfo CreateTypeInfoFromSerializerTypedMethod 
=
+        typeof(TypeResolver).GetMethod(
+            nameof(CreateTypeInfoFromSerializerTyped),
+            BindingFlags.NonPublic | BindingFlags.Static)!;
+    private static readonly MethodInfo CreateNullableSerializerTypeInfoMethod =
+        typeof(TypeResolver).GetMethod(
+            nameof(CreateNullableSerializerTypeInfo),
+            BindingFlags.NonPublic | BindingFlags.Instance)!;
     private static readonly HashSet<TypeId> CompatibleStructAllowedWireTypes =
     [
         TypeId.Struct,
@@ -90,12 +99,12 @@ public sealed class TypeResolver
         where TSerializer : Serializer<T>, new()
     {
         Type type = typeof(T);
-        GeneratedFactories[type] = CreateSerializer<TSerializer>;
+        GeneratedFactories[type] = static _ => TypeInfo.Create(typeof(T), new 
TSerializer());
     }
 
     public Serializer<T> GetSerializer<T>()
     {
-        return GetTypeInfo<T>().Serializer.RequireSerializer<T>();
+        return GetTypeInfo<T>().RequireSerializer<T>();
     }
 
     public TypeInfo GetTypeInfo(Type type)
@@ -108,11 +117,63 @@ public sealed class TypeResolver
         return GetTypeInfo(typeof(T));
     }
 
-    private TypeInfo GetOrCreateTypeInfo(Type type, Serializer? 
explicitSerializer)
+    internal bool IsNoneObject(TypeInfo typeInfo, object? value)
+    {
+        return typeInfo.IsNullableType && value is null;
+    }
+
+    internal void WriteDataObject(TypeInfo typeInfo, WriteContext context, 
object? value, bool hasGenerics)
+    {
+        typeInfo.WriteDataObject(context, value, hasGenerics);
+    }
+
+    internal object? ReadDataObject(TypeInfo typeInfo, ReadContext context)
+    {
+        return typeInfo.ReadDataObject(context);
+    }
+
+    public void WriteObject(
+        TypeInfo typeInfo,
+        WriteContext context,
+        object? value,
+        RefMode refMode,
+        bool writeTypeInfo,
+        bool hasGenerics)
+    {
+        typeInfo.WriteObject(context, value, refMode, writeTypeInfo, 
hasGenerics);
+    }
+
+    internal object? ReadObject(TypeInfo typeInfo, ReadContext context, 
RefMode refMode, bool readTypeInfo)
+    {
+        return typeInfo.ReadObject(context, refMode, readTypeInfo);
+    }
+
+    internal void WriteTypeInfo(TypeInfo typeInfo, WriteContext context)
+    {
+        WriteTypeInfoCore(typeInfo.Type, typeInfo, context);
+    }
+
+    internal void ReadTypeInfo(TypeInfo typeInfo, ReadContext context)
+    {
+        ReadTypeInfoCore(typeInfo.Type, typeInfo, context);
+    }
+
+    internal IReadOnlyList<TypeMetaFieldInfo> 
CompatibleTypeMetaFields(TypeInfo typeInfo, bool trackRef)
+    {
+        Type? nullableType = Nullable.GetUnderlyingType(typeInfo.Type);
+        if (nullableType is not null)
+        {
+            return CompatibleTypeMetaFields(GetTypeInfo(nullableType), 
trackRef);
+        }
+
+        return typeInfo.CompatibleTypeMetaFields(trackRef);
+    }
+
+    private TypeInfo GetOrCreateTypeInfo(Type type, TypeInfo? explicitTypeInfo)
     {
         if (_typeInfos.TryGetValue(type, out TypeInfo? existing))
         {
-            if (explicitSerializer is null || 
ReferenceEquals(existing.Serializer, explicitSerializer))
+            if (explicitTypeInfo is null || ReferenceEquals(existing, 
explicitTypeInfo))
             {
                 return existing;
             }
@@ -123,8 +184,12 @@ public sealed class TypeResolver
             }
         }
 
-        Serializer serializer = explicitSerializer ?? CreateBindingCore(type);
-        TypeInfo typeInfo = new(type, serializer);
+        TypeInfo typeInfo = explicitTypeInfo ?? CreateBindingCore(type);
+        if (typeInfo.Type != type)
+        {
+            throw new InvalidDataException($"serializer type mismatch for 
{type}, got {typeInfo.Type}");
+        }
+
         if (_typeInfos.TryGetValue(type, out TypeInfo? previous))
         {
             typeInfo.CopyRegistrationFrom(previous);
@@ -134,34 +199,29 @@ public sealed class TypeResolver
         return typeInfo;
     }
 
-    internal Serializer GetSerializer(Type type)
-    {
-        return GetOrCreateTypeInfo(type, null).Serializer;
-    }
-
-    internal Serializer RegisterSerializer<T, TSerializer>()
+    internal TypeInfo RegisterSerializer<T, TSerializer>()
         where TSerializer : Serializer<T>, new()
     {
-        Serializer serializer = CreateSerializer<TSerializer>();
-        RegisterSerializer(typeof(T), serializer);
-        return serializer;
+        TypeInfo typeInfo = TypeInfo.Create(typeof(T), new TSerializer());
+        RegisterSerializer(typeof(T), typeInfo);
+        return typeInfo;
     }
 
-    internal void RegisterSerializer(Type type, Serializer serializer)
+    internal void RegisterSerializer(Type type, TypeInfo typeInfo)
     {
-        GetOrCreateTypeInfo(type, serializer);
+        GetOrCreateTypeInfo(type, typeInfo);
     }
 
-    internal void Register(Type type, uint id, Serializer? explicitSerializer 
= null)
+    internal void Register(Type type, uint id, TypeInfo? explicitTypeInfo = 
null)
     {
-        TypeInfo typeInfo = GetOrCreateTypeInfo(type, explicitSerializer);
+        TypeInfo typeInfo = GetOrCreateTypeInfo(type, explicitTypeInfo);
         typeInfo.RegisterByTypeId(id);
         _byUserTypeId[id] = typeInfo;
     }
 
-    internal void Register(Type type, string namespaceName, string typeName, 
Serializer? explicitSerializer = null)
+    internal void Register(Type type, string namespaceName, string typeName, 
TypeInfo? explicitTypeInfo = null)
     {
-        TypeInfo typeInfo = GetOrCreateTypeInfo(type, explicitSerializer);
+        TypeInfo typeInfo = GetOrCreateTypeInfo(type, explicitTypeInfo);
         MetaString namespaceMeta = 
MetaStringEncoder.Namespace.Encode(namespaceName, 
TypeMetaEncodings.NamespaceMetaStringEncodings);
         MetaString typeNameMeta = MetaStringEncoder.TypeName.Encode(typeName, 
TypeMetaEncodings.TypeNameMetaStringEncodings);
         typeInfo.RegisterByTypeName(namespaceMeta, typeNameMeta);
@@ -189,17 +249,45 @@ public sealed class TypeResolver
         throw new TypeNotRegisteredException($"{type} is not registered");
     }
 
-    internal void WriteTypeInfo(Type type, Serializer serializer, WriteContext 
context)
+    internal void WriteTypeInfo<T>(Serializer<T> serializer, WriteContext 
context)
+    {
+        Type type = typeof(T);
+        if (type == typeof(object))
+        {
+            throw new InvalidDataException("dynamic Any value type info is 
runtime-only");
+        }
+
+        Type? nullableType = Nullable.GetUnderlyingType(type);
+        if (nullableType is not null)
+        {
+            WriteTypeInfoCore(nullableType, GetTypeInfo(nullableType), 
context);
+            return;
+        }
+
+        TypeInfo typeInfo = GetTypeInfo<T>();
+        WriteTypeInfoCore(type, typeInfo, context);
+    }
+
+    private void WriteTypeInfoCore(Type type, TypeInfo typeInfo, WriteContext 
context)
     {
-        TypeId staticTypeId = serializer.StaticTypeId;
-        if (!staticTypeId.IsUserTypeKind())
+        if (typeInfo.BuiltInTypeId.HasValue)
         {
-            context.Writer.WriteUInt8((byte)staticTypeId);
+            context.Writer.WriteUInt8((byte)typeInfo.BuiltInTypeId.Value);
             return;
         }
 
+        if (!typeInfo.UserTypeKind.HasValue)
+        {
+            throw new InvalidDataException($"type {type} has runtime-only type 
info");
+        }
+
         TypeInfo info = RequireRegisteredTypeInfo(type);
-        TypeId wireTypeId = ResolveWireTypeId(info.StaticTypeId, 
info.RegisterByName, context.Compatible);
+        if (!info.UserTypeKind.HasValue)
+        {
+            throw new InvalidDataException($"registered type {type} is not a 
user type");
+        }
+
+        TypeId wireTypeId = ResolveWireTypeId(info.UserTypeKind.Value, 
info.RegisterByName, context.Compatible);
         context.Writer.WriteUInt8((byte)wireTypeId);
         switch (wireTypeId)
         {
@@ -256,7 +344,28 @@ public sealed class TypeResolver
         }
     }
 
-    internal void ReadTypeInfo(Type type, Serializer serializer, ReadContext 
context)
+    internal void ReadTypeInfo<T>(Serializer<T> serializer, ReadContext 
context)
+    {
+        Type type = typeof(T);
+        if (type == typeof(object))
+        {
+            DynamicTypeInfo dynamicTypeInfo = ReadDynamicTypeInfo(context);
+            context.SetDynamicTypeInfo(type, dynamicTypeInfo);
+            return;
+        }
+
+        Type? nullableType = Nullable.GetUnderlyingType(type);
+        if (nullableType is not null)
+        {
+            ReadTypeInfoCore(nullableType, GetTypeInfo(nullableType), context);
+            return;
+        }
+
+        TypeInfo typeInfo = GetTypeInfo<T>();
+        ReadTypeInfoCore(type, typeInfo, context);
+    }
+
+    private void ReadTypeInfoCore(Type type, TypeInfo typeInfo, ReadContext 
context)
     {
         uint rawTypeId = context.Reader.ReadVarUInt32();
         if (!Enum.IsDefined(typeof(TypeId), rawTypeId))
@@ -265,19 +374,28 @@ public sealed class TypeResolver
         }
 
         TypeId typeId = (TypeId)rawTypeId;
-        TypeId staticTypeId = serializer.StaticTypeId;
-        if (!staticTypeId.IsUserTypeKind())
+        if (typeInfo.BuiltInTypeId.HasValue)
         {
-            if (typeId != staticTypeId)
+            if (typeId != typeInfo.BuiltInTypeId.Value)
             {
-                throw new TypeMismatchException((uint)staticTypeId, rawTypeId);
+                throw new 
TypeMismatchException((uint)typeInfo.BuiltInTypeId.Value, rawTypeId);
             }
 
             return;
         }
 
+        if (!typeInfo.UserTypeKind.HasValue)
+        {
+            throw new InvalidDataException($"type {type} has runtime-only type 
info");
+        }
+
         TypeInfo info = RequireRegisteredTypeInfo(type);
-        HashSet<TypeId> allowed = AllowedWireTypeIds(info.StaticTypeId, 
info.RegisterByName, context.Compatible);
+        if (!info.UserTypeKind.HasValue)
+        {
+            throw new InvalidDataException($"registered type {type} is not a 
user type");
+        }
+
+        HashSet<TypeId> allowed = AllowedWireTypeIds(info.UserTypeKind.Value, 
info.RegisterByName, context.Compatible);
         if (!allowed.Contains(typeId))
         {
             uint expected = 0;
@@ -357,21 +475,33 @@ public sealed class TypeResolver
         }
     }
 
-    internal static TypeId ResolveWireTypeId(TypeId declaredKind, bool 
registerByName, bool compatible)
+    internal static TypeId ResolveWireTypeId(UserTypeKind declaredKind, bool 
registerByName, bool compatible)
     {
-        TypeId baseKind = NormalizeBaseKind(declaredKind);
         if (registerByName)
         {
-            return NamedKind(baseKind, compatible);
+            return declaredKind switch
+            {
+                UserTypeKind.Struct => compatible ? 
TypeId.NamedCompatibleStruct : TypeId.NamedStruct,
+                UserTypeKind.Enum => TypeId.NamedEnum,
+                UserTypeKind.Ext => TypeId.NamedExt,
+                UserTypeKind.TypedUnion => TypeId.NamedUnion,
+                _ => throw new InvalidDataException($"unknown user type kind 
{declaredKind}"),
+            };
         }
 
-        return IdKind(baseKind, compatible);
+        return declaredKind switch
+        {
+            UserTypeKind.Struct => compatible ? TypeId.CompatibleStruct : 
TypeId.Struct,
+            UserTypeKind.Enum => TypeId.Enum,
+            UserTypeKind.Ext => TypeId.Ext,
+            UserTypeKind.TypedUnion => TypeId.TypedUnion,
+            _ => throw new InvalidDataException($"unknown user type kind 
{declaredKind}"),
+        };
     }
 
-    internal static HashSet<TypeId> AllowedWireTypeIds(TypeId declaredKind, 
bool registerByName, bool compatible)
+    internal static HashSet<TypeId> AllowedWireTypeIds(UserTypeKind 
declaredKind, bool registerByName, bool compatible)
     {
-        TypeId baseKind = NormalizeBaseKind(declaredKind);
-        if (baseKind == TypeId.Struct && compatible)
+        if (declaredKind == UserTypeKind.Struct && compatible)
         {
             return CompatibleStructAllowedWireTypes;
         }
@@ -400,14 +530,14 @@ public sealed class TypeResolver
         return ReadRegisteredValue(typeInfo, context, compatibleTypeMeta);
     }
 
-    private static object? ReadRegisteredValue(TypeInfo typeInfo, ReadContext 
context, TypeMeta? compatibleTypeMeta)
+    private object? ReadRegisteredValue(TypeInfo typeInfo, ReadContext 
context, TypeMeta? compatibleTypeMeta)
     {
         if (compatibleTypeMeta is not null)
         {
             context.PushCompatibleTypeMeta(typeInfo.Type, compatibleTypeMeta);
         }
 
-        return typeInfo.Serializer.ReadObject(context, RefMode.None, false);
+        return ReadObject(typeInfo, context, RefMode.None, false);
     }
 
     public DynamicTypeInfo ReadDynamicTypeInfo(ReadContext context)
@@ -752,50 +882,17 @@ public sealed class TypeResolver
         return values;
     }
 
-    private static TypeId NormalizeBaseKind(TypeId kind)
-    {
-        return kind switch
-        {
-            TypeId.NamedEnum => TypeId.Enum,
-            TypeId.CompatibleStruct or TypeId.NamedCompatibleStruct or 
TypeId.NamedStruct => TypeId.Struct,
-            TypeId.NamedExt => TypeId.Ext,
-            TypeId.NamedUnion => TypeId.TypedUnion,
-            _ => kind,
-        };
-    }
-
-    private static TypeId NamedKind(TypeId baseKind, bool compatible)
-    {
-        return baseKind switch
-        {
-            TypeId.Struct => compatible ? TypeId.NamedCompatibleStruct : 
TypeId.NamedStruct,
-            TypeId.Enum => TypeId.NamedEnum,
-            TypeId.Ext => TypeId.NamedExt,
-            TypeId.TypedUnion => TypeId.NamedUnion,
-            _ => baseKind,
-        };
-    }
-
-    private static TypeId IdKind(TypeId baseKind, bool compatible)
-    {
-        return baseKind switch
-        {
-            TypeId.Struct => compatible ? TypeId.CompatibleStruct : 
TypeId.Struct,
-            _ => baseKind,
-        };
-    }
-
     private static bool WireTypeNeedsUserTypeId(TypeId typeId)
     {
         return typeId is TypeId.Enum or TypeId.Struct or TypeId.Ext or 
TypeId.TypedUnion;
     }
 
-    private static TypeMeta BuildCompatibleTypeMeta(
+    private TypeMeta BuildCompatibleTypeMeta(
         TypeInfo info,
         TypeId wireTypeId,
         bool trackRef)
     {
-        IReadOnlyList<TypeMetaFieldInfo> fields = 
info.Serializer.CompatibleTypeMetaFields(trackRef);
+        IReadOnlyList<TypeMetaFieldInfo> fields = 
CompatibleTypeMetaFields(info, trackRef);
         bool hasFieldsMeta = fields.Count > 0;
         if (info.RegisterByName)
         {
@@ -998,312 +1095,312 @@ public sealed class TypeResolver
         return result;
     }
 
-    private Serializer CreateBindingCore(Type type)
+    private TypeInfo CreateBindingCore(Type type)
     {
-        if (GeneratedFactories.TryGetValue(type, out Func<Serializer>? 
generatedFactory))
+        if (GeneratedFactories.TryGetValue(type, out Func<TypeResolver, 
TypeInfo>? generatedFactory))
         {
-            return generatedFactory();
+            return generatedFactory(this);
         }
 
         if (type == typeof(bool))
         {
-            return new BoolSerializer();
+            return TypeInfo.Create(type, new BoolSerializer());
         }
 
         if (type == typeof(sbyte))
         {
-            return new Int8Serializer();
+            return TypeInfo.Create(type, new Int8Serializer());
         }
 
         if (type == typeof(short))
         {
-            return new Int16Serializer();
+            return TypeInfo.Create(type, new Int16Serializer());
         }
 
         if (type == typeof(int))
         {
-            return new Int32Serializer();
+            return TypeInfo.Create(type, new Int32Serializer());
         }
 
         if (type == typeof(long))
         {
-            return new Int64Serializer();
+            return TypeInfo.Create(type, new Int64Serializer());
         }
 
         if (type == typeof(byte))
         {
-            return new UInt8Serializer();
+            return TypeInfo.Create(type, new UInt8Serializer());
         }
 
         if (type == typeof(ushort))
         {
-            return new UInt16Serializer();
+            return TypeInfo.Create(type, new UInt16Serializer());
         }
 
         if (type == typeof(uint))
         {
-            return new UInt32Serializer();
+            return TypeInfo.Create(type, new UInt32Serializer());
         }
 
         if (type == typeof(ulong))
         {
-            return new UInt64Serializer();
+            return TypeInfo.Create(type, new UInt64Serializer());
         }
 
         if (type == typeof(float))
         {
-            return new Float32Serializer();
+            return TypeInfo.Create(type, new Float32Serializer());
         }
 
         if (type == typeof(double))
         {
-            return new Float64Serializer();
+            return TypeInfo.Create(type, new Float64Serializer());
         }
 
         if (type == typeof(string))
         {
-            return new StringSerializer();
+            return TypeInfo.Create(type, new StringSerializer());
         }
 
         if (type == typeof(byte[]))
         {
-            return new BinarySerializer();
+            return TypeInfo.Create(type, new BinarySerializer());
         }
 
         if (type == typeof(bool[]))
         {
-            return new BoolArraySerializer();
+            return TypeInfo.Create(type, new BoolArraySerializer());
         }
 
         if (type == typeof(sbyte[]))
         {
-            return new Int8ArraySerializer();
+            return TypeInfo.Create(type, new Int8ArraySerializer());
         }
 
         if (type == typeof(short[]))
         {
-            return new Int16ArraySerializer();
+            return TypeInfo.Create(type, new Int16ArraySerializer());
         }
 
         if (type == typeof(int[]))
         {
-            return new Int32ArraySerializer();
+            return TypeInfo.Create(type, new Int32ArraySerializer());
         }
 
         if (type == typeof(long[]))
         {
-            return new Int64ArraySerializer();
+            return TypeInfo.Create(type, new Int64ArraySerializer());
         }
 
         if (type == typeof(ushort[]))
         {
-            return new UInt16ArraySerializer();
+            return TypeInfo.Create(type, new UInt16ArraySerializer());
         }
 
         if (type == typeof(uint[]))
         {
-            return new UInt32ArraySerializer();
+            return TypeInfo.Create(type, new UInt32ArraySerializer());
         }
 
         if (type == typeof(ulong[]))
         {
-            return new UInt64ArraySerializer();
+            return TypeInfo.Create(type, new UInt64ArraySerializer());
         }
 
         if (type == typeof(float[]))
         {
-            return new Float32ArraySerializer();
+            return TypeInfo.Create(type, new Float32ArraySerializer());
         }
 
         if (type == typeof(double[]))
         {
-            return new Float64ArraySerializer();
+            return TypeInfo.Create(type, new Float64ArraySerializer());
         }
 
         if (type == typeof(DateOnly))
         {
-            return new DateOnlySerializer();
+            return TypeInfo.Create(type, new DateOnlySerializer());
         }
 
         if (type == typeof(DateTimeOffset))
         {
-            return new DateTimeOffsetSerializer();
+            return TypeInfo.Create(type, new DateTimeOffsetSerializer());
         }
 
         if (type == typeof(DateTime))
         {
-            return new DateTimeSerializer();
+            return TypeInfo.Create(type, new DateTimeSerializer());
         }
 
         if (type == typeof(TimeSpan))
         {
-            return new TimeSpanSerializer();
+            return TypeInfo.Create(type, new TimeSpanSerializer());
         }
 
         if (type == typeof(List<bool>))
         {
-            return new ListBoolSerializer();
+            return TypeInfo.Create(type, new ListBoolSerializer());
         }
 
         if (type == typeof(List<sbyte>))
         {
-            return new ListInt8Serializer();
+            return TypeInfo.Create(type, new ListInt8Serializer());
         }
 
         if (type == typeof(List<short>))
         {
-            return new ListInt16Serializer();
+            return TypeInfo.Create(type, new ListInt16Serializer());
         }
 
         if (type == typeof(List<int>))
         {
-            return new ListIntSerializer();
+            return TypeInfo.Create(type, new ListIntSerializer());
         }
 
         if (type == typeof(List<long>))
         {
-            return new ListLongSerializer();
+            return TypeInfo.Create(type, new ListLongSerializer());
         }
 
         if (type == typeof(List<byte>))
         {
-            return new ListUInt8Serializer();
+            return TypeInfo.Create(type, new ListUInt8Serializer());
         }
 
         if (type == typeof(List<ushort>))
         {
-            return new ListUInt16Serializer();
+            return TypeInfo.Create(type, new ListUInt16Serializer());
         }
 
         if (type == typeof(List<uint>))
         {
-            return new ListUIntSerializer();
+            return TypeInfo.Create(type, new ListUIntSerializer());
         }
 
         if (type == typeof(List<ulong>))
         {
-            return new ListULongSerializer();
+            return TypeInfo.Create(type, new ListULongSerializer());
         }
 
         if (type == typeof(List<float>))
         {
-            return new ListFloatSerializer();
+            return TypeInfo.Create(type, new ListFloatSerializer());
         }
 
         if (type == typeof(List<double>))
         {
-            return new ListDoubleSerializer();
+            return TypeInfo.Create(type, new ListDoubleSerializer());
         }
 
         if (type == typeof(List<string>))
         {
-            return new ListStringSerializer();
+            return TypeInfo.Create(type, new ListStringSerializer());
         }
 
         if (type == typeof(List<DateOnly>))
         {
-            return new ListDateOnlySerializer();
+            return TypeInfo.Create(type, new ListDateOnlySerializer());
         }
 
         if (type == typeof(List<DateTimeOffset>))
         {
-            return new ListDateTimeOffsetSerializer();
+            return TypeInfo.Create(type, new ListDateTimeOffsetSerializer());
         }
 
         if (type == typeof(List<DateTime>))
         {
-            return new ListDateTimeSerializer();
+            return TypeInfo.Create(type, new ListDateTimeSerializer());
         }
 
         if (type == typeof(List<TimeSpan>))
         {
-            return new ListTimeSpanSerializer();
+            return TypeInfo.Create(type, new ListTimeSpanSerializer());
         }
 
         if (type == typeof(HashSet<sbyte>))
         {
-            return new SetInt8Serializer();
+            return TypeInfo.Create(type, new SetInt8Serializer());
         }
 
         if (type == typeof(HashSet<short>))
         {
-            return new SetInt16Serializer();
+            return TypeInfo.Create(type, new SetInt16Serializer());
         }
 
         if (type == typeof(HashSet<int>))
         {
-            return new SetIntSerializer();
+            return TypeInfo.Create(type, new SetIntSerializer());
         }
 
         if (type == typeof(HashSet<long>))
         {
-            return new SetLongSerializer();
+            return TypeInfo.Create(type, new SetLongSerializer());
         }
 
         if (type == typeof(HashSet<byte>))
         {
-            return new SetUInt8Serializer();
+            return TypeInfo.Create(type, new SetUInt8Serializer());
         }
 
         if (type == typeof(HashSet<ushort>))
         {
-            return new SetUInt16Serializer();
+            return TypeInfo.Create(type, new SetUInt16Serializer());
         }
 
         if (type == typeof(HashSet<uint>))
         {
-            return new SetUIntSerializer();
+            return TypeInfo.Create(type, new SetUIntSerializer());
         }
 
         if (type == typeof(HashSet<ulong>))
         {
-            return new SetULongSerializer();
+            return TypeInfo.Create(type, new SetULongSerializer());
         }
 
         if (type == typeof(HashSet<float>))
         {
-            return new SetFloatSerializer();
+            return TypeInfo.Create(type, new SetFloatSerializer());
         }
 
         if (type == typeof(HashSet<double>))
         {
-            return new SetDoubleSerializer();
+            return TypeInfo.Create(type, new SetDoubleSerializer());
         }
 
-        Serializer? primitiveCollectionSerializer = 
TryCreatePrimitiveCollectionSerializer(type);
-        if (primitiveCollectionSerializer is not null)
+        TypeInfo? primitiveCollectionTypeInfo = 
TryCreatePrimitiveCollectionTypeInfo(type);
+        if (primitiveCollectionTypeInfo is not null)
         {
-            return primitiveCollectionSerializer;
+            return primitiveCollectionTypeInfo;
         }
 
-        Serializer? primitiveDictionarySerializer = 
TryCreatePrimitiveDictionarySerializer(type);
-        if (primitiveDictionarySerializer is not null)
+        TypeInfo? primitiveDictionaryTypeInfo = 
TryCreatePrimitiveDictionaryTypeInfo(type);
+        if (primitiveDictionaryTypeInfo is not null)
         {
-            return primitiveDictionarySerializer;
+            return primitiveDictionaryTypeInfo;
         }
 
         if (type == typeof(object))
         {
-            return new DynamicAnyObjectSerializer();
+            return TypeInfo.Create(type, new DynamicAnyObjectSerializer());
         }
 
         if (typeof(Union).IsAssignableFrom(type))
         {
             Type serializerType = 
typeof(UnionSerializer<>).MakeGenericType(type);
-            return CreateSerializer(serializerType);
+            return CreateTypeInfo(type, serializerType);
         }
 
         if (type.IsEnum)
         {
             Type serializerType = 
typeof(EnumSerializer<>).MakeGenericType(type);
-            return CreateSerializer(serializerType);
+            return CreateTypeInfo(type, serializerType);
         }
 
         if (type.IsArray)
         {
             Type elementType = type.GetElementType()!;
             Type serializerType = 
typeof(ArraySerializer<>).MakeGenericType(elementType);
-            return CreateSerializer(serializerType);
+            return CreateTypeInfo(type, serializerType);
         }
 
         if (type.IsGenericType)
@@ -1312,87 +1409,86 @@ public sealed class TypeResolver
             Type[] genericArgs = type.GetGenericArguments();
             if (genericType == typeof(Nullable<>))
             {
-                Type serializerType = 
typeof(NullableSerializer<>).MakeGenericType(genericArgs[0]);
-                return CreateSerializer(serializerType);
+                return CreateNullableTypeInfo(genericArgs[0]);
             }
 
             if (genericType == typeof(List<>))
             {
                 Type serializerType = 
typeof(ListSerializer<>).MakeGenericType(genericArgs[0]);
-                return CreateSerializer(serializerType);
+                return CreateTypeInfo(type, serializerType);
             }
 
             if (genericType == typeof(HashSet<>))
             {
                 Type serializerType = 
typeof(SetSerializer<>).MakeGenericType(genericArgs[0]);
-                return CreateSerializer(serializerType);
+                return CreateTypeInfo(type, serializerType);
             }
 
             if (genericType == typeof(SortedSet<>))
             {
                 Type serializerType = 
typeof(SortedSetSerializer<>).MakeGenericType(genericArgs[0]);
-                return CreateSerializer(serializerType);
+                return CreateTypeInfo(type, serializerType);
             }
 
             if (genericType == typeof(ImmutableHashSet<>))
             {
                 Type serializerType = 
typeof(ImmutableHashSetSerializer<>).MakeGenericType(genericArgs[0]);
-                return CreateSerializer(serializerType);
+                return CreateTypeInfo(type, serializerType);
             }
 
             if (genericType == typeof(LinkedList<>))
             {
                 Type serializerType = 
typeof(LinkedListSerializer<>).MakeGenericType(genericArgs[0]);
-                return CreateSerializer(serializerType);
+                return CreateTypeInfo(type, serializerType);
             }
 
             if (genericType == typeof(Queue<>))
             {
                 Type serializerType = 
typeof(QueueSerializer<>).MakeGenericType(genericArgs[0]);
-                return CreateSerializer(serializerType);
+                return CreateTypeInfo(type, serializerType);
             }
 
             if (genericType == typeof(Stack<>))
             {
                 Type serializerType = 
typeof(StackSerializer<>).MakeGenericType(genericArgs[0]);
-                return CreateSerializer(serializerType);
+                return CreateTypeInfo(type, serializerType);
             }
 
             if (genericType == typeof(Dictionary<,>))
             {
                 Type serializerType = 
typeof(DictionarySerializer<,>).MakeGenericType(genericArgs[0], genericArgs[1]);
-                return CreateSerializer(serializerType);
+                return CreateTypeInfo(type, serializerType);
             }
 
             if (genericType == typeof(SortedDictionary<,>))
             {
                 Type serializerType = 
typeof(SortedDictionarySerializer<,>).MakeGenericType(genericArgs[0], 
genericArgs[1]);
-                return CreateSerializer(serializerType);
+                return CreateTypeInfo(type, serializerType);
             }
 
             if (genericType == typeof(SortedList<,>))
             {
                 Type serializerType = 
typeof(SortedListSerializer<,>).MakeGenericType(genericArgs[0], genericArgs[1]);
-                return CreateSerializer(serializerType);
+                return CreateTypeInfo(type, serializerType);
             }
 
             if (genericType == typeof(ConcurrentDictionary<,>))
             {
                 Type serializerType = 
typeof(ConcurrentDictionarySerializer<,>).MakeGenericType(genericArgs[0], 
genericArgs[1]);
-                return CreateSerializer(serializerType);
+                return CreateTypeInfo(type, serializerType);
             }
 
             if (genericType == typeof(NullableKeyDictionary<,>))
             {
                 Type serializerType = 
typeof(NullableKeyDictionarySerializer<,>).MakeGenericType(genericArgs[0], 
genericArgs[1]);
-                return CreateSerializer(serializerType);
+                return CreateTypeInfo(type, serializerType);
             }
         }
 
         throw new TypeNotRegisteredException($"No serializer available for 
{type}");
     }
 
-    private Serializer? TryCreatePrimitiveCollectionSerializer(Type type)
+    private TypeInfo? TryCreatePrimitiveCollectionTypeInfo(Type type)
     {
         if (!type.IsGenericType)
         {
@@ -1411,7 +1507,7 @@ public sealed class TypeResolver
                 : genericType == typeof(Queue<>)
                     ? 
typeof(PrimitiveQueueSerializer<,>).MakeGenericType(elementType, listLikeCodec)
                     : 
typeof(PrimitiveStackSerializer<,>).MakeGenericType(elementType, listLikeCodec);
-            return CreateSerializer(serializerType);
+            return CreateTypeInfo(type, serializerType);
         }
 
         if ((genericType == typeof(SortedSet<>) ||
@@ -1421,13 +1517,13 @@ public sealed class TypeResolver
             Type serializerType = genericType == typeof(SortedSet<>)
                 ? 
typeof(PrimitiveSortedSetSerializer<,>).MakeGenericType(elementType, setCodec)
                 : 
typeof(PrimitiveImmutableHashSetSerializer<,>).MakeGenericType(elementType, 
setCodec);
-            return CreateSerializer(serializerType);
+            return CreateTypeInfo(type, serializerType);
         }
 
         return null;
     }
 
-    private Serializer? TryCreatePrimitiveDictionarySerializer(Type type)
+    private TypeInfo? TryCreatePrimitiveDictionaryTypeInfo(Type type)
     {
         if (!type.IsGenericType)
         {
@@ -1457,7 +1553,7 @@ public sealed class TypeResolver
                     : genericType == typeof(SortedList<,>)
                         ? 
typeof(PrimitiveStringKeySortedListSerializer<,>).MakeGenericType(valueType, 
valueCodecType)
                         : 
typeof(PrimitiveStringKeyConcurrentDictionarySerializer<,>).MakeGenericType(valueType,
 valueCodecType);
-            return CreateSerializer(serializerType);
+            return CreateTypeInfo(type, serializerType);
         }
 
         if (keyType == valueType &&
@@ -1470,38 +1566,80 @@ public sealed class TypeResolver
                     : genericType == typeof(SortedList<,>)
                         ? 
typeof(PrimitiveSameTypeSortedListSerializer<,>).MakeGenericType(valueType, 
sameTypeCodec)
                         : 
typeof(PrimitiveSameTypeConcurrentDictionarySerializer<,>).MakeGenericType(valueType,
 sameTypeCodec);
-            return CreateSerializer(serializerType);
+            return CreateTypeInfo(type, serializerType);
         }
 
         return null;
     }
 
-    private static Serializer CreateSerializer<TSerializer>()
-        where TSerializer : Serializer, new()
+    private TypeInfo CreateTypeInfo(Type expectedType, Type serializerType)
     {
-        return new TSerializer();
+        object serializer;
+        try
+        {
+            serializer = Activator.CreateInstance(serializerType)
+                ?? throw new InvalidDataException($"failed to create 
serializer for {serializerType}");
+        }
+        catch (Exception ex)
+        {
+            throw new InvalidDataException($"failed to create serializer for 
{serializerType}: {ex.Message}");
+        }
+
+        return CreateTypeInfo(expectedType, serializer);
     }
 
-    private Serializer CreateSerializer(Type serializerType)
+    private static TypeInfo CreateTypeInfo(Type expectedType, object 
serializer)
     {
-        if (!typeof(Serializer).IsAssignableFrom(serializerType))
+        Type? valueType = ResolveSerializerValueType(serializer.GetType());
+        if (valueType is null)
         {
-            throw new InvalidDataException($"{serializerType} is not a 
serializer");
+            throw new InvalidDataException($"{serializer.GetType()} is not a 
serializer");
         }
 
-        try
+        if (valueType != expectedType)
         {
-            if (Activator.CreateInstance(serializerType) is Serializer 
serializer)
-            {
-                return serializer;
-            }
+            throw new InvalidDataException($"serializer type mismatch for 
{expectedType}, got {valueType}");
         }
-        catch (Exception ex)
+
+        MethodInfo createMethod = 
CreateTypeInfoFromSerializerTypedMethod.MakeGenericMethod(valueType);
+        return (TypeInfo)createMethod.Invoke(null, [serializer])!;
+    }
+
+    private static TypeInfo CreateTypeInfoFromSerializerTyped<T>(object 
serializerObject)
+    {
+        if (serializerObject is not Serializer<T> serializer)
         {
-            throw new InvalidDataException($"failed to create serializer for 
{serializerType}: {ex.Message}");
+            throw new InvalidDataException($"serializer type mismatch for 
{typeof(T)}");
+        }
+
+        return TypeInfo.Create(typeof(T), serializer);
+    }
+
+    private TypeInfo CreateNullableTypeInfo(Type valueType)
+    {
+        MethodInfo createMethod = 
CreateNullableSerializerTypeInfoMethod.MakeGenericMethod(valueType);
+        return (TypeInfo)createMethod.Invoke(this, null)!;
+    }
+
+    private TypeInfo CreateNullableSerializerTypeInfo<T>() where T : struct
+    {
+        return TypeInfo.Create(typeof(T?), new NullableSerializer<T>());
+    }
+
+    private static Type? ResolveSerializerValueType(Type serializerType)
+    {
+        Type? current = serializerType;
+        while (current is not null)
+        {
+            if (current.IsGenericType && current.GetGenericTypeDefinition() == 
typeof(Serializer<>))
+            {
+                return current.GetGenericArguments()[0];
+            }
+
+            current = current.BaseType;
         }
 
-        throw new InvalidDataException($"{serializerType} is not a 
serializer");
+        return null;
     }
 
     private static MetaString ReadMetaString(ByteReader reader, 
MetaStringDecoder decoder, IReadOnlyList<MetaStringEncoding> encodings)
diff --git a/csharp/src/Fory/UnionSerializer.cs 
b/csharp/src/Fory/UnionSerializer.cs
index 00a960317..90d4a1970 100644
--- a/csharp/src/Fory/UnionSerializer.cs
+++ b/csharp/src/Fory/UnionSerializer.cs
@@ -25,19 +25,11 @@ public sealed class UnionSerializer<TUnion> : 
Serializer<TUnion>
 {
     private static readonly Func<int, object?, TUnion> Factory = 
BuildFactory();
 
-    public override TypeId StaticTypeId => TypeId.TypedUnion;
 
-    public override bool IsNullableType => true;
 
-    public override bool IsReferenceTrackableType => true;
 
     public override TUnion DefaultValue => null!;
 
-    public override bool IsNone(in TUnion value)
-    {
-        return value is null;
-    }
-
     public override void WriteData(WriteContext context, in TUnion value, bool 
hasGenerics)
     {
         _ = hasGenerics;
diff --git a/csharp/tests/Fory.XlangPeer/Program.cs 
b/csharp/tests/Fory.XlangPeer/Program.cs
index e0c6a1200..60fece54b 100644
--- a/csharp/tests/Fory.XlangPeer/Program.cs
+++ b/csharp/tests/Fory.XlangPeer/Program.cs
@@ -1027,11 +1027,7 @@ public sealed class MyExt
 
 public sealed class MyExtSerializer : Serializer<MyExt>
 {
-    public override TypeId StaticTypeId => TypeId.Ext;
-    public override bool IsNullableType => true;
-    public override bool IsReferenceTrackableType => true;
     public override MyExt DefaultValue => null!;
-    public override bool IsNone(in MyExt value) => value is null;
 
     public override void WriteData(WriteContext context, in MyExt value, bool 
hasGenerics)
     {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to