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 5ac50690a feat(c#): add max depth and code lint support (#3428)
5ac50690a is described below

commit 5ac50690a08b47038c95c43ec4319e68e08b9352
Author: Shawn Yang <[email protected]>
AuthorDate: Fri Feb 27 00:06:54 2026 +0800

    feat(c#): add max depth and code lint support (#3428)
    
    ## Why?
    
    - Add a hard limit for dynamic object nesting depth during
    deserialization to prevent unbounded recursive reads.
    - Simplify the C# dynamic payload API surface and align C# formatting
    checks with CI.
    
    ## What does this PR do?
    
    - Adds dynamic read-depth tracking/enforcement in `ReadContext` and
    dynamic any deserialization (`AnySerializer`), wired to
    `Config.MaxDepth`.
    - Removes `SerializeObject` / `DeserializeObject` from `Fory` and
    `ThreadSafeFory`; dynamic payloads now use `Serialize<object?>` /
    `Deserialize<object?>`.
    - Updates C# runtime tests, xlang peer test program, README, and C#
    guides to the generic object API.
    - Adds depth-limit coverage tests
    (`DynamicObjectReadDepthExceededThrows`,
    `DynamicObjectReadDepthWithinLimitRoundTrip`).
    - Adds C# formatting support/checks via `csharp/.editorconfig`,
    `ci/format.sh`, and CI workflow steps (`dotnet format
    --verify-no-changes`).
    - Adds analyzer release metadata files to `Fory.Generator` and includes
    them in the project file.
    
    ## Related issues
    
    #3387
    
    ## 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
    
    N/A
---
 .github/workflows/ci.yml                           |  10 +
 AGENTS.md                                          |  39 +++-
 ci/format.sh                                       |  45 ++++-
 csharp/.editorconfig                               |  51 +++++
 csharp/README.md                                   |   4 +-
 .../src/Fory.Generator/AnalyzerReleases.Shipped.md |   6 +
 .../Fory.Generator/AnalyzerReleases.Unshipped.md   |   7 +
 csharp/src/Fory.Generator/Fory.Generator.csproj    |   5 +
 csharp/src/Fory.Generator/ForyObjectGenerator.cs   |   8 +-
 csharp/src/Fory/AnySerializer.cs                   |  72 ++++---
 csharp/src/Fory/Attributes.cs                      |  21 ++
 csharp/src/Fory/Config.cs                          |  41 +++-
 csharp/src/Fory/Context.cs                         |  30 ++-
 csharp/src/Fory/FieldSkipper.cs                    |  40 ++--
 csharp/src/Fory/Fory.cs                            | 159 +++++++++-------
 csharp/src/Fory/ForyException.cs                   |  53 +++++-
 csharp/src/Fory/OptionalSerializer.cs              |  30 +--
 csharp/src/Fory/PrimitiveDictionarySerializers.cs  |   2 +-
 csharp/src/Fory/Serializer.cs                      |  63 ++++--
 csharp/src/Fory/StringSerializer.cs                |   6 +-
 csharp/src/Fory/ThreadSafeFory.cs                  |  84 ++++++--
 csharp/src/Fory/TypeResolver.cs                    | 212 ++++++++++-----------
 csharp/tests/Fory.Tests/ForyRuntimeTests.cs        |  47 +++--
 csharp/tests/Fory.XlangPeer/Program.cs             | 136 ++++++-------
 docs/guide/csharp/basic-serialization.md           |  12 +-
 docs/guide/csharp/index.md                         |   4 +-
 docs/guide/csharp/supported-types.md               |   2 +-
 python/setup.py                                    |  81 +++++++-
 28 files changed, 885 insertions(+), 385 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b0db1328c..21b99a3a5 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -167,6 +167,12 @@ jobs:
         run: |
           cd csharp
           dotnet test tests/Fory.Tests/Fory.Tests.csproj -c Release --no-build
+      - name: Verify C# format
+        run: |
+          cd csharp
+          dotnet format Fory.sln --verify-no-changes \
+            --exclude src/Fory.Generator/AnalyzerReleases.Shipped.md \
+            --exclude src/Fory.Generator/AnalyzerReleases.Unshipped.md
 
   csharp_xlang:
     name: C# Xlang Test
@@ -907,6 +913,10 @@ jobs:
         uses: actions/setup-python@v5
         with:
           python-version: 3.8
+      - name: Set up .NET 8
+        uses: actions/setup-dotnet@v4
+        with:
+          dotnet-version: "8.0.x"
       - name: Use Node.js 20.x
         uses: actions/setup-node@v4
         with:
diff --git a/AGENTS.md b/AGENTS.md
index db711837a..398237fd0 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -63,6 +63,42 @@ mvn -T16 test
 mvn -T16 test -Dtest=org.apache.fory.TestClass#testMethod
 ```
 
+### C# Development
+
+- All dotnet commands must be executed within the `csharp` directory.
+- All changes to `csharp` must pass formatting and tests.
+- Fory C# requires .NET SDK `8.0+` and C# `12+`.
+- Use `dotnet format` to keep C# code style consistent.
+
+```bash
+# Restore
+dotnet restore Fory.sln
+
+# Build
+dotnet build Fory.sln -c Release --no-restore
+
+# Run tests
+dotnet test Fory.sln -c Release
+
+# Run specific test
+dotnet test tests/Fory.Tests/Fory.Tests.csproj -c Release --filter 
"FullyQualifiedName~ForyRuntimeTests.DynamicObjectReadDepthExceededThrows"
+
+# Format code
+dotnet format Fory.sln
+
+# Format check
+dotnet format Fory.sln --verify-no-changes
+```
+
+Run C# xlang tests:
+
+```bash
+cd java
+mvn -T16 install -DskipTests
+cd fory-core
+FORY_CSHARP_JAVA_CI=1 ENABLE_FORY_DEBUG_OUTPUT=1 mvn -T16 test 
-Dtest=org.apache.fory.xlang.CSharpXlangTest
+```
+
 ### C++ Development
 
 - All commands must be executed within the `cpp` directory.
@@ -389,6 +425,7 @@ The `origin` points to forked repository instead of the 
official repository.
 
 - **Language Implementations**:
   - `java/`: Java implementation (maven-based, multi-module)
+  - `csharp/`: C# implementation (.NET SDK + source generator)
   - `python/`: Python implementation (pip/setuptools + bazel)
   - `cpp/`: C++ implementation (bazel-based)
   - `go/`: Go implementation (go modules)
@@ -609,7 +646,7 @@ Fory rust provides macro-based serialization and 
deserialization. Fory rust cons
 
 - **Unit Tests**: Focus on internal behavior verification
 - **Integration Tests**: Use `integration_tests/` for cross-language 
compatibility
-- **Language alignment and protocol compatibility**: Run 
`org.apache.fory.xlang.CPPXlangTest`, `org.apache.fory.xlang.RustXlangTest`, 
`org.apache.fory.xlang.GoXlangTest`, and 
`org.apache.fory.xlang.PythonXlangTest` when changing xlang or type mapping 
behavior
+- **Language alignment and protocol compatibility**: Run 
`org.apache.fory.xlang.CPPXlangTest`, `org.apache.fory.xlang.CSharpXlangTest`, 
`org.apache.fory.xlang.RustXlangTest`, `org.apache.fory.xlang.GoXlangTest`, and 
`org.apache.fory.xlang.PythonXlangTest` when changing xlang or type mapping 
behavior
 - **Performance Tests**: Include benchmarks for performance-critical changes
 
 ### Documentation Requirements
diff --git a/ci/format.sh b/ci/format.sh
index 0545ea231..877b9845e 100755
--- a/ci/format.sh
+++ b/ci/format.sh
@@ -213,6 +213,21 @@ format_go() {
     fi
 }
 
+format_csharp() {
+    echo "$(date)" "dotnet format C# files...."
+    if command -v dotnet >/dev/null; then
+      pushd "$ROOT/csharp"
+      dotnet format Fory.sln \
+        --exclude src/Fory.Generator/AnalyzerReleases.Shipped.md \
+        --exclude src/Fory.Generator/AnalyzerReleases.Unshipped.md
+      popd
+      echo "$(date)" "C# formatting done!"
+    else
+      echo "ERROR: dotnet is not installed! Install .NET SDK from 
https://dotnet.microsoft.com/download";
+      exit 1
+    fi
+}
+
 format_swift() {
     echo "$(date)" "SwiftLint check Swift files...."
     if command -v swiftlint >/dev/null; then
@@ -251,6 +266,11 @@ format_all() {
       git ls-files -- '*.go' "${GIT_LS_EXCLUDES[@]}" | xargs -P 5 gofmt -w
     fi
 
+    echo "$(date)" "format csharp...."
+    if command -v dotnet >/dev/null; then
+      format_csharp
+    fi
+
     echo "$(date)" "lint swift...."
     format_swift
 
@@ -295,6 +315,14 @@ format_changed() {
         fi
     fi
 
+    if command -v dotnet >/dev/null; then
+        local csharp_changed
+        csharp_changed="$(git diff --name-only --diff-filter=ACRM "$MERGEBASE" 
-- csharp || true)"
+        if [ -n "$csharp_changed" ]; then
+            format_csharp
+        fi
+    fi
+
     if which node >/dev/null; then
         pushd "$ROOT"
         if ! git diff --diff-filter=ACRM --quiet --exit-code "$MERGEBASE" -- 
'*.ts' &>/dev/null; then
@@ -303,8 +331,17 @@ format_changed() {
         fi
         # Install prettier globally
         npm install -g prettier
-        # Fix markdown files
-        prettier --write "**/*.md"
+        # Fix markdown files except analyzer release tracking files.
+        # Exclude symlinks (for example CLAUDE.md) because prettier fails on 
explicitly passed symlink paths.
+        git ls-files -z -- '*.md' \
+            ':!:csharp/src/Fory.Generator/AnalyzerReleases.Shipped.md' \
+            ':!:csharp/src/Fory.Generator/AnalyzerReleases.Unshipped.md' \
+            | while IFS= read -r -d '' file; do
+                if [ ! -L "$file" ]; then
+                    printf '%s\0' "$file"
+                fi
+            done \
+            | xargs -0 prettier --write
         popd
     fi
 
@@ -337,6 +374,10 @@ elif [ "${1-}" == '--go' ]; then
     format_go
 elif [ "${1-}" == '--swift' ]; then
     format_swift
+elif [ "${1-}" == '--csharp' ]; then
+    format_csharp
+elif [ "${1-}" == '--swift' ]; then
+    format_swift
 else
     # Add the origin remote if it doesn't exist
     if ! git remote -v | grep -q origin; then
diff --git a/csharp/.editorconfig b/csharp/.editorconfig
new file mode 100644
index 000000000..95f4897a3
--- /dev/null
+++ b/csharp/.editorconfig
@@ -0,0 +1,51 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+[*.{cs,csx}]
+indent_size = 4
+tab_width = 4
+
+dotnet_sort_system_directives_first = true
+dotnet_separate_import_directive_groups = false
+
+csharp_new_line_before_open_brace = all
+csharp_indent_case_contents = true
+csharp_indent_switch_labels = true
+csharp_indent_labels = one_less_than_current
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = false
+
+csharp_space_after_cast = false
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_comma = true
+csharp_space_after_dot = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_after_semicolon_in_for_statement = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_before_comma = false
+csharp_space_before_dot = false
+csharp_space_before_open_square_brackets = false
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_between_empty_method_call_parentheses = false
+csharp_space_between_empty_method_declaration_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_between_square_brackets = false
diff --git a/csharp/README.md b/csharp/README.md
index 12bcdecef..d419341ce 100644
--- a/csharp/README.md
+++ b/csharp/README.md
@@ -154,8 +154,8 @@ Dictionary<object, object?> map = new()
     [true] = null,
 };
 
-byte[] payload = fory.SerializeObject(map);
-object? decoded = fory.DeserializeObject(payload);
+byte[] payload = fory.Serialize<object?>(map);
+object? decoded = fory.Deserialize<object?>(payload);
 ```
 
 ### 5. Thread-Safe Runtime
diff --git a/csharp/src/Fory.Generator/AnalyzerReleases.Shipped.md 
b/csharp/src/Fory.Generator/AnalyzerReleases.Shipped.md
new file mode 100644
index 000000000..ed666bef5
--- /dev/null
+++ b/csharp/src/Fory.Generator/AnalyzerReleases.Shipped.md
@@ -0,0 +1,6 @@
+## Release 0.0.0
+
+### New Rules
+
+| Rule ID | Category | Severity | Notes |
+| ------- | -------- | -------- | ----- |
diff --git a/csharp/src/Fory.Generator/AnalyzerReleases.Unshipped.md 
b/csharp/src/Fory.Generator/AnalyzerReleases.Unshipped.md
new file mode 100644
index 000000000..b1d4b0610
--- /dev/null
+++ b/csharp/src/Fory.Generator/AnalyzerReleases.Unshipped.md
@@ -0,0 +1,7 @@
+### New Rules
+
+| Rule ID | Category | Severity | Notes |
+| ------- | -------- | -------- | ----- |
+| FORY001 | Fory | Error | Generic types are not supported by ForyObject 
generator |
+| FORY002 | Fory | Error | Missing parameterless constructor |
+| FORY003 | Fory | Error | Unsupported Field encoding |
diff --git a/csharp/src/Fory.Generator/Fory.Generator.csproj 
b/csharp/src/Fory.Generator/Fory.Generator.csproj
index 48864e20c..265d94481 100644
--- a/csharp/src/Fory.Generator/Fory.Generator.csproj
+++ b/csharp/src/Fory.Generator/Fory.Generator.csproj
@@ -9,6 +9,11 @@
     <IncludeBuildOutput>false</IncludeBuildOutput>
   </PropertyGroup>
 
+  <ItemGroup>
+    <AdditionalFiles Include="AnalyzerReleases.Shipped.md" />
+    <AdditionalFiles Include="AnalyzerReleases.Unshipped.md" />
+  </ItemGroup>
+
   <ItemGroup>
     <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" 
Version="3.11.0" PrivateAssets="all" />
     <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" 
PrivateAssets="all" />
diff --git a/csharp/src/Fory.Generator/ForyObjectGenerator.cs 
b/csharp/src/Fory.Generator/ForyObjectGenerator.cs
index 808e6ef6b..ea8e76244 100644
--- a/csharp/src/Fory.Generator/ForyObjectGenerator.cs
+++ b/csharp/src/Fory.Generator/ForyObjectGenerator.cs
@@ -33,7 +33,7 @@ public sealed class ForyObjectGenerator : 
IIncrementalGenerator
     private static readonly DiagnosticDescriptor GenericTypeNotSupported = new(
         id: "FORY001",
         title: "Generic types are not supported by ForyObject generator",
-        messageFormat: "Type '{0}' is generic and is not supported by 
[ForyObject].",
+        messageFormat: "Type '{0}' is generic and is not supported by 
[ForyObject]",
         category: "Fory",
         defaultSeverity: DiagnosticSeverity.Error,
         isEnabledByDefault: true);
@@ -41,7 +41,7 @@ public sealed class ForyObjectGenerator : 
IIncrementalGenerator
     private static readonly DiagnosticDescriptor MissingCtor = new(
         id: "FORY002",
         title: "Missing parameterless constructor",
-        messageFormat: "Class '{0}' must declare an accessible parameterless 
constructor for [ForyObject].",
+        messageFormat: "Class '{0}' must declare an accessible parameterless 
constructor for [ForyObject]",
         category: "Fory",
         defaultSeverity: DiagnosticSeverity.Error,
         isEnabledByDefault: true);
@@ -49,7 +49,7 @@ public sealed class ForyObjectGenerator : 
IIncrementalGenerator
     private static readonly DiagnosticDescriptor UnsupportedEncoding = new(
         id: "FORY003",
         title: "Unsupported Field encoding",
-        messageFormat: "Member '{0}' uses unsupported [Field] encoding for 
type '{1}'.",
+        messageFormat: "Member '{0}' uses unsupported [Field] encoding for 
type '{1}'",
         category: "Fory",
         defaultSeverity: DiagnosticSeverity.Error,
         isEnabledByDefault: true);
@@ -505,7 +505,7 @@ public sealed class ForyObjectGenerator : 
IIncrementalGenerator
         sb.AppendLine($"                global::Apache.Fory.TypeInfo 
{typeInfoVar};");
         sb.AppendLine($"                if (__Fory{cacheId}DictRuntimeType == 
{runtimeTypeVar} && __Fory{cacheId}DictTypeInfo is not null)");
         sb.AppendLine("                {");
-            sb.AppendLine($"                    {typeInfoVar} = 
__Fory{cacheId}DictTypeInfo;");
+        sb.AppendLine($"                    {typeInfoVar} = 
__Fory{cacheId}DictTypeInfo;");
         sb.AppendLine("                }");
         sb.AppendLine("                else");
         sb.AppendLine("                {");
diff --git a/csharp/src/Fory/AnySerializer.cs b/csharp/src/Fory/AnySerializer.cs
index 77b6e80b2..d73a80da9 100644
--- a/csharp/src/Fory/AnySerializer.cs
+++ b/csharp/src/Fory/AnySerializer.cs
@@ -88,29 +88,25 @@ public sealed class DynamicAnyObjectSerializer : 
Serializer<object?>
                 case RefFlag.Null:
                     return null;
                 case RefFlag.Ref:
-                {
-                    uint refId = context.Reader.ReadVarUInt32();
-                    return context.RefReader.ReadRefValue(refId);
-                }
-                case RefFlag.RefValue:
-                {
-                    uint reservedRefId = context.RefReader.ReserveRefId();
-                    context.RefReader.PushPendingReference(reservedRefId);
-                    if (readTypeInfo)
                     {
-                        ReadAnyTypeInfo(context);
+                        uint refId = context.Reader.ReadVarUInt32();
+                        return context.RefReader.ReadRefValue(refId);
                     }
-
-                    object? value = ReadData(context);
-                    if (readTypeInfo)
+                case RefFlag.RefValue:
                     {
-                        context.ClearDynamicTypeInfo(typeof(object));
+                        uint reservedRefId = context.RefReader.ReserveRefId();
+                        context.RefReader.PushPendingReference(reservedRefId);
+                        try
+                        {
+                            object? value = ReadNonNullDynamicAny(context, 
readTypeInfo);
+                            
context.RefReader.FinishPendingReferenceIfNeeded(value);
+                            return value;
+                        }
+                        finally
+                        {
+                            context.RefReader.PopPendingReference();
+                        }
                     }
-
-                    context.RefReader.FinishPendingReferenceIfNeeded(value);
-                    context.RefReader.PopPendingReference();
-                    return value;
-                }
                 case RefFlag.NotNullValue:
                     break;
                 default:
@@ -118,18 +114,7 @@ public sealed class DynamicAnyObjectSerializer : 
Serializer<object?>
             }
         }
 
-        if (readTypeInfo)
-        {
-            ReadAnyTypeInfo(context);
-        }
-
-        object? result = ReadData(context);
-        if (readTypeInfo)
-        {
-            context.ClearDynamicTypeInfo(typeof(object));
-        }
-
-        return result;
+        return ReadNonNullDynamicAny(context, readTypeInfo);
     }
 
     private static bool AnyValueIsReferenceTrackable(object value, 
TypeResolver typeResolver)
@@ -143,6 +128,31 @@ public sealed class DynamicAnyObjectSerializer : 
Serializer<object?>
         DynamicTypeInfo typeInfo = 
context.TypeResolver.ReadDynamicTypeInfo(context);
         context.SetDynamicTypeInfo(typeof(object), typeInfo);
     }
+
+    private object? ReadNonNullDynamicAny(ReadContext context, bool 
readTypeInfo)
+    {
+        context.IncreaseDynamicReadDepth();
+        bool loadedDynamicTypeInfo = false;
+        try
+        {
+            if (readTypeInfo)
+            {
+                ReadAnyTypeInfo(context);
+                loadedDynamicTypeInfo = true;
+            }
+
+            return ReadData(context);
+        }
+        finally
+        {
+            if (loadedDynamicTypeInfo)
+            {
+                context.ClearDynamicTypeInfo(typeof(object));
+            }
+
+            context.DecreaseDynamicReadDepth();
+        }
+    }
 }
 
 public static class DynamicAnyCodec
diff --git a/csharp/src/Fory/Attributes.cs b/csharp/src/Fory/Attributes.cs
index 84fe5dc38..2d6749fd9 100644
--- a/csharp/src/Fory/Attributes.cs
+++ b/csharp/src/Fory/Attributes.cs
@@ -17,20 +17,41 @@
 
 namespace Apache.Fory;
 
+/// <summary>
+/// Marks a class, struct, or enum as a generated Fory object type.
+/// </summary>
 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | 
AttributeTargets.Enum)]
 public sealed class ForyObjectAttribute : Attribute
 {
 }
 
+/// <summary>
+/// Specifies field-level integer/number encoding strategy for generated 
serializers.
+/// </summary>
 public enum FieldEncoding
 {
+    /// <summary>
+    /// Variable-length integer encoding.
+    /// </summary>
     Varint,
+    /// <summary>
+    /// Fixed-width integer encoding.
+    /// </summary>
     Fixed,
+    /// <summary>
+    /// Tagged field encoding for schema-evolution scenarios.
+    /// </summary>
     Tagged,
 }
 
+/// <summary>
+/// Overrides generated serializer behavior for a field or property.
+/// </summary>
 [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
 public sealed class FieldAttribute : Attribute
 {
+    /// <summary>
+    /// Gets or sets the field encoding strategy used by generated serializers.
+    /// </summary>
     public FieldEncoding Encoding { get; set; } = FieldEncoding.Varint;
 }
diff --git a/csharp/src/Fory/Config.cs b/csharp/src/Fory/Config.cs
index 5cde5d848..4745053e0 100644
--- a/csharp/src/Fory/Config.cs
+++ b/csharp/src/Fory/Config.cs
@@ -17,6 +17,14 @@
 
 namespace Apache.Fory;
 
+/// <summary>
+/// Immutable runtime configuration used by <see cref="Fory"/> and <see 
cref="ThreadSafeFory"/>.
+/// </summary>
+/// <param name="Xlang">Whether cross-language protocol mode is 
enabled.</param>
+/// <param name="TrackRef">Whether shared and circular reference tracking is 
enabled.</param>
+/// <param name="Compatible">Whether schema-compatible mode is enabled.</param>
+/// <param name="CheckStructVersion">Whether generated struct schema hash 
checks are enforced.</param>
+/// <param name="MaxDepth">Maximum allowed nesting depth for dynamic object 
payload reads.</param>
 public sealed record Config(
     bool Xlang = true,
     bool TrackRef = false,
@@ -24,6 +32,9 @@ public sealed record Config(
     bool CheckStructVersion = false,
     int MaxDepth = 20);
 
+/// <summary>
+/// Fluent builder for creating <see cref="Fory"/> and <see 
cref="ThreadSafeFory"/> runtimes.
+/// </summary>
 public sealed class ForyBuilder
 {
     private bool _xlang = true;
@@ -32,30 +43,56 @@ public sealed class ForyBuilder
     private bool _checkStructVersion;
     private int _maxDepth = 20;
 
+    /// <summary>
+    /// Enables or disables cross-language protocol mode.
+    /// </summary>
+    /// <param name="enabled">Whether to enable cross-language mode. Defaults 
to <c>true</c>.</param>
+    /// <returns>The same builder instance.</returns>
     public ForyBuilder Xlang(bool enabled = true)
     {
         _xlang = enabled;
         return this;
     }
 
+    /// <summary>
+    /// Enables or disables reference tracking for shared and circular object 
graphs.
+    /// </summary>
+    /// <param name="enabled">Whether to enable reference tracking. Defaults 
to <c>false</c>.</param>
+    /// <returns>The same builder instance.</returns>
     public ForyBuilder TrackRef(bool enabled = false)
     {
         _trackRef = enabled;
         return this;
     }
 
+    /// <summary>
+    /// Enables or disables schema-compatible mode for schema evolution 
scenarios.
+    /// </summary>
+    /// <param name="enabled">Whether to enable compatible mode. Defaults to 
<c>false</c>.</param>
+    /// <returns>The same builder instance.</returns>
     public ForyBuilder Compatible(bool enabled = false)
     {
         _compatible = enabled;
         return this;
     }
 
+    /// <summary>
+    /// Enables or disables generated struct schema hash validation.
+    /// </summary>
+    /// <param name="enabled">Whether to enforce struct version checks. 
Defaults to <c>false</c>.</param>
+    /// <returns>The same builder instance.</returns>
     public ForyBuilder CheckStructVersion(bool enabled = false)
     {
         _checkStructVersion = enabled;
         return this;
     }
 
+    /// <summary>
+    /// Sets the maximum supported dynamic object nesting depth during 
deserialization.
+    /// </summary>
+    /// <param name="value">Depth limit. Must be greater than <c>0</c>.</param>
+    /// <returns>The same builder instance.</returns>
+    /// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref 
name="value"/> is less than or equal to <c>0</c>.</exception>
     public ForyBuilder MaxDepth(int value)
     {
         if (value <= 0)
@@ -78,8 +115,9 @@ public sealed class ForyBuilder
     }
 
     /// <summary>
-    /// Builds a single-thread <see cref="Fory"/> instance.
+    /// Builds a single-threaded <see cref="Fory"/> instance.
     /// </summary>
+    /// <returns>A configured <see cref="Fory"/> runtime.</returns>
     public Fory Build()
     {
         return new Fory(BuildConfig());
@@ -88,6 +126,7 @@ public sealed class ForyBuilder
     /// <summary>
     /// Builds a multi-thread-safe wrapper that keeps one <see cref="Fory"/> 
per thread.
     /// </summary>
+    /// <returns>A configured <see cref="ThreadSafeFory"/> runtime.</returns>
     public ThreadSafeFory BuildThreadSafe()
     {
         return new ThreadSafeFory(BuildConfig());
diff --git a/csharp/src/Fory/Context.cs b/csharp/src/Fory/Context.cs
index f07466dee..990d89674 100644
--- a/csharp/src/Fory/Context.cs
+++ b/csharp/src/Fory/Context.cs
@@ -229,6 +229,8 @@ public sealed class ReadContext
     private readonly Dictionary<Type, List<TypeMeta>> 
_pendingCompatibleTypeMeta = [];
     private readonly Dictionary<Type, DynamicTypeInfo> _pendingDynamicTypeInfo 
= [];
     private readonly Dictionary<CanonicalReferenceSignature, 
List<CanonicalReferenceEntry>> _canonicalReferenceCache = [];
+    private readonly int _maxDynamicReadDepth;
+    private int _currentDynamicReadDepth;
 
     public ReadContext(
         ByteReader reader,
@@ -237,8 +239,14 @@ public sealed class ReadContext
         bool compatible = false,
         bool checkStructVersion = false,
         CompatibleTypeDefReadState? compatibleTypeDefState = null,
-        MetaStringReadState? metaStringReadState = null)
+        MetaStringReadState? metaStringReadState = null,
+        int maxDynamicReadDepth = 20)
     {
+        if (maxDynamicReadDepth <= 0)
+        {
+            throw new ArgumentOutOfRangeException(nameof(maxDynamicReadDepth), 
"MaxDepth must be greater than 0.");
+        }
+
         Reader = reader;
         TypeResolver = typeResolver;
         TrackRef = trackRef;
@@ -247,6 +255,7 @@ public sealed class ReadContext
         RefReader = new RefReader();
         CompatibleTypeDefState = compatibleTypeDefState ?? new 
CompatibleTypeDefReadState();
         MetaStringReadState = metaStringReadState ?? new MetaStringReadState();
+        _maxDynamicReadDepth = maxDynamicReadDepth;
     }
 
     public ByteReader Reader { get; private set; }
@@ -322,6 +331,24 @@ public sealed class ReadContext
         _pendingDynamicTypeInfo.Remove(type);
     }
 
+    public void IncreaseDynamicReadDepth()
+    {
+        _currentDynamicReadDepth += 1;
+        if (_currentDynamicReadDepth > _maxDynamicReadDepth)
+        {
+            throw new InvalidDataException(
+                $"maximum dynamic object nesting depth 
({_maxDynamicReadDepth}) exceeded. current depth: {_currentDynamicReadDepth}");
+        }
+    }
+
+    public void DecreaseDynamicReadDepth()
+    {
+        if (_currentDynamicReadDepth > 0)
+        {
+            _currentDynamicReadDepth -= 1;
+        }
+    }
+
     public T CanonicalizeNonTrackingReference<T>(T value, int start, int end)
     {
         if (!TrackRef || end <= start || value is null || value is not object 
obj)
@@ -361,6 +388,7 @@ public sealed class ReadContext
         _pendingCompatibleTypeMeta.Clear();
         _pendingDynamicTypeInfo.Clear();
         _canonicalReferenceCache.Clear();
+        _currentDynamicReadDepth = 0;
     }
 
     public void Reset()
diff --git a/csharp/src/Fory/FieldSkipper.cs b/csharp/src/Fory/FieldSkipper.cs
index f5ccae21b..a2549f7e1 100644
--- a/csharp/src/Fory/FieldSkipper.cs
+++ b/csharp/src/Fory/FieldSkipper.cs
@@ -73,34 +73,34 @@ public static class FieldSkipper
             case (uint)TypeId.String:
                 return 
context.TypeResolver.GetSerializer<string>().Read(context, refMode, false);
             case (uint)TypeId.List:
-            {
-                if (fieldType.Generics.Count != 1 || 
fieldType.Generics[0].TypeId != (uint)TypeId.String)
                 {
-                    throw new InvalidDataException("unsupported compatible 
list element type");
-                }
+                    if (fieldType.Generics.Count != 1 || 
fieldType.Generics[0].TypeId != (uint)TypeId.String)
+                    {
+                        throw new InvalidDataException("unsupported compatible 
list element type");
+                    }
 
-                return 
context.TypeResolver.GetSerializer<List<string>>().Read(context, refMode, 
false);
-            }
+                    return 
context.TypeResolver.GetSerializer<List<string>>().Read(context, refMode, 
false);
+                }
             case (uint)TypeId.Set:
-            {
-                if (fieldType.Generics.Count != 1 || 
fieldType.Generics[0].TypeId != (uint)TypeId.String)
                 {
-                    throw new InvalidDataException("unsupported compatible set 
element type");
-                }
+                    if (fieldType.Generics.Count != 1 || 
fieldType.Generics[0].TypeId != (uint)TypeId.String)
+                    {
+                        throw new InvalidDataException("unsupported compatible 
set element type");
+                    }
 
-                return 
context.TypeResolver.GetSerializer<HashSet<string>>().Read(context, refMode, 
false);
-            }
+                    return 
context.TypeResolver.GetSerializer<HashSet<string>>().Read(context, refMode, 
false);
+                }
             case (uint)TypeId.Map:
-            {
-                if (fieldType.Generics.Count != 2 ||
-                    fieldType.Generics[0].TypeId != (uint)TypeId.String ||
-                    fieldType.Generics[1].TypeId != (uint)TypeId.String)
                 {
-                    throw new InvalidDataException("unsupported compatible map 
key/value type");
-                }
+                    if (fieldType.Generics.Count != 2 ||
+                        fieldType.Generics[0].TypeId != (uint)TypeId.String ||
+                        fieldType.Generics[1].TypeId != (uint)TypeId.String)
+                    {
+                        throw new InvalidDataException("unsupported compatible 
map key/value type");
+                    }
 
-                return context.TypeResolver.GetSerializer<Dictionary<string, 
string>>().Read(context, refMode, false);
-            }
+                    return 
context.TypeResolver.GetSerializer<Dictionary<string, string>>().Read(context, 
refMode, false);
+                }
             case (uint)TypeId.Enum:
                 return ReadEnumOrdinal(context, refMode);
             case (uint)TypeId.Union:
diff --git a/csharp/src/Fory/Fory.cs b/csharp/src/Fory/Fory.cs
index 412c17c61..d428ec2bc 100644
--- a/csharp/src/Fory/Fory.cs
+++ b/csharp/src/Fory/Fory.cs
@@ -49,34 +49,68 @@ public sealed class Fory
             Config.Compatible,
             Config.CheckStructVersion,
             new CompatibleTypeDefReadState(),
-            new MetaStringReadState());
+            new MetaStringReadState(),
+            Config.MaxDepth);
     }
 
+    /// <summary>
+    /// Gets the immutable runtime configuration.
+    /// </summary>
     public Config Config { get; }
 
+    /// <summary>
+    /// Creates a new <see cref="ForyBuilder"/> for configuring and building 
runtimes.
+    /// </summary>
+    /// <returns>A new builder instance.</returns>
     public static ForyBuilder Builder()
     {
         return new ForyBuilder();
     }
 
+    /// <summary>
+    /// Registers a user type by numeric type identifier.
+    /// </summary>
+    /// <typeparam name="T">Type to register.</typeparam>
+    /// <param name="typeId">Numeric type identifier used on the wire.</param>
+    /// <returns>The same runtime instance.</returns>
     public Fory Register<T>(uint typeId)
     {
         _typeResolver.Register(typeof(T), typeId);
         return this;
     }
 
+    /// <summary>
+    /// Registers a user type by name using an empty namespace.
+    /// </summary>
+    /// <typeparam name="T">Type to register.</typeparam>
+    /// <param name="typeName">Type name used on the wire.</param>
+    /// <returns>The same runtime instance.</returns>
     public Fory Register<T>(string typeName)
     {
         _typeResolver.Register(typeof(T), string.Empty, typeName);
         return this;
     }
 
+    /// <summary>
+    /// Registers a user type by namespace and name.
+    /// </summary>
+    /// <typeparam name="T">Type to register.</typeparam>
+    /// <param name="typeNamespace">Namespace used on the wire.</param>
+    /// <param name="typeName">Type name used on the wire.</param>
+    /// <returns>The same runtime instance.</returns>
     public Fory Register<T>(string typeNamespace, string typeName)
     {
         _typeResolver.Register(typeof(T), typeNamespace, typeName);
         return this;
     }
 
+    /// <summary>
+    /// Registers a user type by numeric type identifier with a custom 
serializer.
+    /// </summary>
+    /// <typeparam name="T">Type to register.</typeparam>
+    /// <typeparam name="TSerializer">Serializer implementation used for 
<typeparamref name="T"/>.</typeparam>
+    /// <param name="typeId">Numeric type identifier used on the wire.</param>
+    /// <returns>The same runtime instance.</returns>
     public Fory Register<T, TSerializer>(uint typeId)
         where TSerializer : Serializer<T>, new()
     {
@@ -85,6 +119,14 @@ public sealed class Fory
         return this;
     }
 
+    /// <summary>
+    /// Registers a user type by namespace and name with a custom serializer.
+    /// </summary>
+    /// <typeparam name="T">Type to register.</typeparam>
+    /// <typeparam name="TSerializer">Serializer implementation used for 
<typeparamref name="T"/>.</typeparam>
+    /// <param name="typeNamespace">Namespace used on the wire.</param>
+    /// <param name="typeName">Type name used on the wire.</param>
+    /// <returns>The same runtime instance.</returns>
     public Fory Register<T, TSerializer>(string typeNamespace, string typeName)
         where TSerializer : Serializer<T>, new()
     {
@@ -93,6 +135,12 @@ public sealed class Fory
         return this;
     }
 
+    /// <summary>
+    /// Serializes a value into a new byte array containing one Fory frame.
+    /// </summary>
+    /// <typeparam name="T">Value type.</typeparam>
+    /// <param name="value">Value to serialize.</param>
+    /// <returns>Serialized bytes.</returns>
     public byte[] Serialize<T>(in T value)
     {
         ByteWriter writer = _writeContext.Writer;
@@ -112,12 +160,25 @@ public sealed class Fory
         return writer.ToArray();
     }
 
+    /// <summary>
+    /// Serializes a value and writes one Fory frame into the provided buffer 
writer.
+    /// </summary>
+    /// <typeparam name="T">Value type.</typeparam>
+    /// <param name="output">Destination writer.</param>
+    /// <param name="value">Value to serialize.</param>
     public void Serialize<T>(IBufferWriter<byte> output, in T value)
     {
         byte[] payload = Serialize(value);
         output.Write(payload);
     }
 
+    /// <summary>
+    /// Deserializes a value from one Fory frame in the provided span.
+    /// </summary>
+    /// <typeparam name="T">Target type.</typeparam>
+    /// <param name="payload">Serialized bytes containing exactly one 
frame.</param>
+    /// <returns>Deserialized value.</returns>
+    /// <exception cref="InvalidDataException">Thrown when trailing bytes 
remain after decoding.</exception>
     public T Deserialize<T>(ReadOnlySpan<byte> payload)
     {
         ByteReader reader = _readContext.Reader;
@@ -131,6 +192,13 @@ public sealed class Fory
         return value;
     }
 
+    /// <summary>
+    /// Deserializes a value from one Fory frame in the provided byte array.
+    /// </summary>
+    /// <typeparam name="T">Target type.</typeparam>
+    /// <param name="payload">Serialized bytes containing exactly one 
frame.</param>
+    /// <returns>Deserialized value.</returns>
+    /// <exception cref="InvalidDataException">Thrown when trailing bytes 
remain after decoding.</exception>
     public T Deserialize<T>(byte[] payload)
     {
         ByteReader reader = _readContext.Reader;
@@ -144,6 +212,12 @@ public sealed class Fory
         return value;
     }
 
+    /// <summary>
+    /// Deserializes a value from the head of a framed sequence and advances 
the sequence.
+    /// </summary>
+    /// <typeparam name="T">Target type.</typeparam>
+    /// <param name="payload">Input sequence. On success, sliced past the 
consumed frame.</param>
+    /// <returns>Deserialized value.</returns>
     public T Deserialize<T>(ref ReadOnlySequence<byte> payload)
     {
         byte[] bytes = payload.ToArray();
@@ -154,65 +228,12 @@ public sealed class Fory
         return value;
     }
 
-    public byte[] SerializeObject(object? value)
-    {
-        ByteWriter writer = _writeContext.Writer;
-        writer.Reset();
-        bool isNone = value is null;
-        WriteHead(writer, isNone);
-        if (!isNone)
-        {
-            _writeContext.ResetFor(writer);
-            RefMode refMode = Config.TrackRef ? RefMode.Tracking : 
RefMode.NullOnly;
-            DynamicAnyCodec.WriteAny(_writeContext, value, refMode, true, 
false);
-            _writeContext.ResetObjectState();
-        }
-
-        return writer.ToArray();
-    }
-
-    public void SerializeObject(IBufferWriter<byte> output, object? value)
-    {
-        byte[] payload = SerializeObject(value);
-        output.Write(payload);
-    }
-
-    public object? DeserializeObject(ReadOnlySpan<byte> payload)
-    {
-        ByteReader reader = _readContext.Reader;
-        reader.Reset(payload);
-        object? value = DeserializeObjectFromReader(reader);
-        if (reader.Remaining != 0)
-        {
-            throw new InvalidDataException("unexpected trailing bytes after 
deserializing dynamic object");
-        }
-
-        return value;
-    }
-
-    public object? DeserializeObject(byte[] payload)
-    {
-        ByteReader reader = _readContext.Reader;
-        reader.Reset(payload);
-        object? value = DeserializeObjectFromReader(reader);
-        if (reader.Remaining != 0)
-        {
-            throw new InvalidDataException("unexpected trailing bytes after 
deserializing dynamic object");
-        }
-
-        return value;
-    }
-
-    public object? DeserializeObject(ref ReadOnlySequence<byte> payload)
-    {
-        byte[] bytes = payload.ToArray();
-        ByteReader reader = _readContext.Reader;
-        reader.Reset(bytes);
-        object? value = DeserializeObjectFromReader(reader);
-        payload = payload.Slice(reader.Cursor);
-        return value;
-    }
 
+    /// <summary>
+    /// Writes the frame header for a payload.
+    /// </summary>
+    /// <param name="writer">Destination writer.</param>
+    /// <param name="isNone">Whether the payload value is null.</param>
     public void WriteHead(ByteWriter writer, bool isNone)
     {
         byte bitmap = 0;
@@ -229,6 +250,12 @@ public sealed class Fory
         writer.WriteUInt8(bitmap);
     }
 
+    /// <summary>
+    /// Reads and validates the frame header.
+    /// </summary>
+    /// <param name="reader">Source reader.</param>
+    /// <returns><c>true</c> if the payload value is null; otherwise 
<c>false</c>.</returns>
+    /// <exception cref="InvalidDataException">Thrown when the peer xlang 
bitmap does not match this runtime mode.</exception>
     public bool ReadHead(ByteReader reader)
     {
         byte bitmap = reader.ReadUInt8();
@@ -257,18 +284,4 @@ public sealed class Fory
         return value;
     }
 
-    private object? DeserializeObjectFromReader(ByteReader reader)
-    {
-        bool isNone = ReadHead(reader);
-        if (isNone)
-        {
-            return null;
-        }
-
-        _readContext.ResetFor(reader);
-        RefMode refMode = Config.TrackRef ? RefMode.Tracking : 
RefMode.NullOnly;
-        object? value = DynamicAnyCodec.ReadAny(_readContext, refMode, true);
-        _readContext.ResetObjectState();
-        return value;
-    }
 }
diff --git a/csharp/src/Fory/ForyException.cs b/csharp/src/Fory/ForyException.cs
index d3c4689ff..22883a5a0 100644
--- a/csharp/src/Fory/ForyException.cs
+++ b/csharp/src/Fory/ForyException.cs
@@ -17,54 +17,105 @@
 
 namespace Apache.Fory;
 
+/// <summary>
+/// Base exception type for Apache Fory C# runtime errors.
+/// </summary>
 public class ForyException : Exception
 {
+    /// <summary>
+    /// Creates a new Fory exception with the provided message.
+    /// </summary>
+    /// <param name="message">Error description.</param>
     public ForyException(string message) : base(message)
     {
     }
 }
 
+/// <summary>
+/// Thrown when input data is malformed or inconsistent with expected wire 
format.
+/// </summary>
 public sealed class InvalidDataException : ForyException
 {
+    /// <summary>
+    /// Creates a new invalid data exception.
+    /// </summary>
+    /// <param name="message">Invalid data details.</param>
     public InvalidDataException(string message) : base($"Invalid data: 
{message}")
     {
     }
 }
 
+/// <summary>
+/// Thrown when received type metadata does not match expected type metadata.
+/// </summary>
 public sealed class TypeMismatchException : ForyException
 {
+    /// <summary>
+    /// Creates a new type mismatch exception.
+    /// </summary>
+    /// <param name="expected">Expected wire type id.</param>
+    /// <param name="actual">Actual wire type id.</param>
     public TypeMismatchException(uint expected, uint actual)
         : base($"Type mismatch: expected {expected}, got {actual}")
     {
     }
 }
 
+/// <summary>
+/// Thrown when an unregistered type is serialized or deserialized in a 
registered-type path.
+/// </summary>
 public sealed class TypeNotRegisteredException : ForyException
 {
+    /// <summary>
+    /// Creates a new type-not-registered exception.
+    /// </summary>
+    /// <param name="message">Unregistered type details.</param>
     public TypeNotRegisteredException(string message) : base($"Type not 
registered: {message}")
     {
     }
 }
 
+/// <summary>
+/// Thrown when reference metadata is invalid or inconsistent.
+/// </summary>
 public sealed class RefException : ForyException
 {
+    /// <summary>
+    /// Creates a new reference exception.
+    /// </summary>
+    /// <param name="message">Reference error details.</param>
     public RefException(string message) : base($"Reference error: {message}")
     {
     }
 }
 
+/// <summary>
+/// Thrown when encoding or decoding logic encounters unsupported or invalid 
state.
+/// </summary>
 public sealed class EncodingException : ForyException
 {
+    /// <summary>
+    /// Creates a new encoding exception.
+    /// </summary>
+    /// <param name="message">Encoding error details.</param>
     public EncodingException(string message) : base($"Encoding error: 
{message}")
     {
     }
 }
 
+/// <summary>
+/// Thrown when buffer cursor movement would exceed available bounds.
+/// </summary>
 public sealed class OutOfBoundsException : ForyException
 {
+    /// <summary>
+    /// Creates a new out-of-bounds exception.
+    /// </summary>
+    /// <param name="cursor">Current cursor offset.</param>
+    /// <param name="need">Requested byte count.</param>
+    /// <param name="length">Buffer length.</param>
     public OutOfBoundsException(int cursor, int need, int length)
         : base($"Buffer out of bounds: cursor={cursor}, need={need}, 
length={length}")
     {
     }
 }
-
diff --git a/csharp/src/Fory/OptionalSerializer.cs 
b/csharp/src/Fory/OptionalSerializer.cs
index 9cb42c7eb..ef3271b93 100644
--- a/csharp/src/Fory/OptionalSerializer.cs
+++ b/csharp/src/Fory/OptionalSerializer.cs
@@ -85,26 +85,26 @@ public sealed class NullableSerializer<T> : Serializer<T?> 
where T : struct
             case RefMode.None:
                 return wrappedSerializer.Read(context, RefMode.None, 
readTypeInfo);
             case RefMode.NullOnly:
-            {
-                sbyte refFlag = context.Reader.ReadInt8();
-                if (refFlag == (sbyte)RefFlag.Null)
                 {
-                    return null;
-                }
+                    sbyte refFlag = context.Reader.ReadInt8();
+                    if (refFlag == (sbyte)RefFlag.Null)
+                    {
+                        return null;
+                    }
 
-                return wrappedSerializer.Read(context, RefMode.None, 
readTypeInfo);
-            }
+                    return wrappedSerializer.Read(context, RefMode.None, 
readTypeInfo);
+                }
             case RefMode.Tracking:
-            {
-                sbyte refFlag = context.Reader.ReadInt8();
-                if (refFlag == (sbyte)RefFlag.Null)
                 {
-                    return null;
-                }
+                    sbyte refFlag = context.Reader.ReadInt8();
+                    if (refFlag == (sbyte)RefFlag.Null)
+                    {
+                        return null;
+                    }
 
-                context.Reader.MoveBack(1);
-                return wrappedSerializer.Read(context, RefMode.Tracking, 
readTypeInfo);
-            }
+                    context.Reader.MoveBack(1);
+                    return wrappedSerializer.Read(context, RefMode.Tracking, 
readTypeInfo);
+                }
             default:
                 throw new InvalidDataException($"unsupported ref mode 
{refMode}");
         }
diff --git a/csharp/src/Fory/PrimitiveDictionarySerializers.cs 
b/csharp/src/Fory/PrimitiveDictionarySerializers.cs
index 3e024b92e..d407cdcde 100644
--- a/csharp/src/Fory/PrimitiveDictionarySerializers.cs
+++ b/csharp/src/Fory/PrimitiveDictionarySerializers.cs
@@ -737,7 +737,7 @@ internal class PrimitiveDictionarySerializer<TKey, TValue, 
TKeyCodec, TValueCode
 
     public override Dictionary<TKey, TValue> DefaultValue => null!;
 
-public override void WriteData(WriteContext context, in Dictionary<TKey, 
TValue> value, bool hasGenerics)
+    public override void WriteData(WriteContext context, in Dictionary<TKey, 
TValue> value, bool hasGenerics)
     {
         Dictionary<TKey, TValue> map = value ?? [];
         PrimitiveDictionaryCodecWriter.WriteMap<
diff --git a/csharp/src/Fory/Serializer.cs b/csharp/src/Fory/Serializer.cs
index a90242a3e..1aa219ea2 100644
--- a/csharp/src/Fory/Serializer.cs
+++ b/csharp/src/Fory/Serializer.cs
@@ -17,16 +17,42 @@
 
 namespace Apache.Fory;
 
+/// <summary>
+/// Base class for custom serializers.
+/// </summary>
+/// <typeparam name="T">Runtime value type handled by this 
serializer.</typeparam>
 public abstract class Serializer<T>
 {
+    /// <summary>
+    /// Gets the default value returned when a null marker is read for this 
serializer.
+    /// </summary>
     public virtual T DefaultValue => default!;
 
     internal object? DefaultObject => DefaultValue;
 
+    /// <summary>
+    /// Writes the serializer-specific payload body.
+    /// </summary>
+    /// <param name="context">Write context.</param>
+    /// <param name="value">Value to encode.</param>
+    /// <param name="hasGenerics">Whether generic type metadata is present for 
the current field path.</param>
     public abstract void WriteData(WriteContext context, in T value, bool 
hasGenerics);
 
+    /// <summary>
+    /// Reads the serializer-specific payload body.
+    /// </summary>
+    /// <param name="context">Read context.</param>
+    /// <returns>Decoded value.</returns>
     public abstract T ReadData(ReadContext context);
 
+    /// <summary>
+    /// Writes reference metadata and optional type metadata, then delegates 
to <see cref="WriteData"/>.
+    /// </summary>
+    /// <param name="context">Write context.</param>
+    /// <param name="value">Value to write.</param>
+    /// <param name="refMode">Reference handling mode.</param>
+    /// <param name="writeTypeInfo">Whether type metadata should be 
written.</param>
+    /// <param name="hasGenerics">Whether generic type metadata is present for 
the current field path.</param>
     public virtual void Write(WriteContext context, in T value, RefMode 
refMode, bool writeTypeInfo, bool hasGenerics)
     {
         if (refMode != RefMode.None)
@@ -63,6 +89,13 @@ public abstract class Serializer<T>
         WriteData(context, value, hasGenerics);
     }
 
+    /// <summary>
+    /// Reads reference metadata and optional type metadata, then delegates to 
<see cref="ReadData"/>.
+    /// </summary>
+    /// <param name="context">Read context.</param>
+    /// <param name="refMode">Reference handling mode.</param>
+    /// <param name="readTypeInfo">Whether type metadata should be 
read.</param>
+    /// <returns>Decoded value.</returns>
     public virtual T Read(ReadContext context, RefMode refMode, bool 
readTypeInfo)
     {
         if (refMode != RefMode.None)
@@ -74,24 +107,24 @@ public abstract class Serializer<T>
                 case RefFlag.Null:
                     return DefaultValue;
                 case RefFlag.Ref:
-                {
-                    uint refId = context.Reader.ReadVarUInt32();
-                    return context.RefReader.ReadRef<T>(refId);
-                }
-                case RefFlag.RefValue:
-                {
-                    uint reservedRefId = context.RefReader.ReserveRefId();
-                    context.RefReader.PushPendingReference(reservedRefId);
-                    if (readTypeInfo)
                     {
-                        context.TypeResolver.ReadTypeInfo(this, context);
+                        uint refId = context.Reader.ReadVarUInt32();
+                        return context.RefReader.ReadRef<T>(refId);
                     }
+                case RefFlag.RefValue:
+                    {
+                        uint reservedRefId = context.RefReader.ReserveRefId();
+                        context.RefReader.PushPendingReference(reservedRefId);
+                        if (readTypeInfo)
+                        {
+                            context.TypeResolver.ReadTypeInfo(this, context);
+                        }
 
-                    T value = ReadData(context);
-                    context.RefReader.FinishPendingReferenceIfNeeded(value);
-                    context.RefReader.PopPendingReference();
-                    return value;
-                }
+                        T value = ReadData(context);
+                        
context.RefReader.FinishPendingReferenceIfNeeded(value);
+                        context.RefReader.PopPendingReference();
+                        return value;
+                    }
                 case RefFlag.NotNullValue:
                     break;
                 default:
diff --git a/csharp/src/Fory/StringSerializer.cs 
b/csharp/src/Fory/StringSerializer.cs
index 1e56fcfd6..a27674422 100644
--- a/csharp/src/Fory/StringSerializer.cs
+++ b/csharp/src/Fory/StringSerializer.cs
@@ -63,9 +63,9 @@ public sealed class StringSerializer : Serializer<string>
         int byteLength = checked((int)(header >> 2));
         ReadOnlySpan<byte> bytes = context.Reader.ReadSpan(byteLength);
         return encoding switch
-            {
-                (ulong)ForyStringEncoding.Utf8 => 
Encoding.UTF8.GetString(bytes),
-                (ulong)ForyStringEncoding.Latin1 => DecodeLatin1(bytes),
+        {
+            (ulong)ForyStringEncoding.Utf8 => Encoding.UTF8.GetString(bytes),
+            (ulong)ForyStringEncoding.Latin1 => DecodeLatin1(bytes),
             (ulong)ForyStringEncoding.Utf16 => DecodeUtf16(bytes),
             _ => throw new EncodingException($"unsupported string encoding 
{encoding}"),
         };
diff --git a/csharp/src/Fory/ThreadSafeFory.cs 
b/csharp/src/Fory/ThreadSafeFory.cs
index 280f34bee..6270d3c8d 100644
--- a/csharp/src/Fory/ThreadSafeFory.cs
+++ b/csharp/src/Fory/ThreadSafeFory.cs
@@ -36,26 +36,55 @@ public sealed class ThreadSafeFory : IDisposable
         _threadLocalFory = new ThreadLocal<Fory>(CreatePerThreadFory, 
trackAllValues: true);
     }
 
+    /// <summary>
+    /// Gets the immutable runtime configuration shared by all thread-local 
runtimes.
+    /// </summary>
     public Config Config => _config;
 
+    /// <summary>
+    /// Registers a user type by numeric type identifier for all current and 
future thread-local runtimes.
+    /// </summary>
+    /// <typeparam name="T">Type to register.</typeparam>
+    /// <param name="typeId">Numeric type identifier used on the wire.</param>
+    /// <returns>The same runtime instance.</returns>
     public ThreadSafeFory Register<T>(uint typeId)
     {
         ApplyRegistration(fory => fory.Register<T>(typeId));
         return this;
     }
 
+    /// <summary>
+    /// Registers a user type by name for all current and future thread-local 
runtimes.
+    /// </summary>
+    /// <typeparam name="T">Type to register.</typeparam>
+    /// <param name="typeName">Type name used on the wire.</param>
+    /// <returns>The same runtime instance.</returns>
     public ThreadSafeFory Register<T>(string typeName)
     {
         ApplyRegistration(fory => fory.Register<T>(typeName));
         return this;
     }
 
+    /// <summary>
+    /// Registers a user type by namespace and name for all current and future 
thread-local runtimes.
+    /// </summary>
+    /// <typeparam name="T">Type to register.</typeparam>
+    /// <param name="typeNamespace">Namespace used on the wire.</param>
+    /// <param name="typeName">Type name used on the wire.</param>
+    /// <returns>The same runtime instance.</returns>
     public ThreadSafeFory Register<T>(string typeNamespace, string typeName)
     {
         ApplyRegistration(fory => fory.Register<T>(typeNamespace, typeName));
         return this;
     }
 
+    /// <summary>
+    /// Registers a user type by numeric type identifier with a custom 
serializer for all thread-local runtimes.
+    /// </summary>
+    /// <typeparam name="T">Type to register.</typeparam>
+    /// <typeparam name="TSerializer">Serializer implementation used for 
<typeparamref name="T"/>.</typeparam>
+    /// <param name="typeId">Numeric type identifier used on the wire.</param>
+    /// <returns>The same runtime instance.</returns>
     public ThreadSafeFory Register<T, TSerializer>(uint typeId)
         where TSerializer : Serializer<T>, new()
     {
@@ -63,6 +92,14 @@ public sealed class ThreadSafeFory : IDisposable
         return this;
     }
 
+    /// <summary>
+    /// Registers a user type by namespace and name with a custom serializer 
for all thread-local runtimes.
+    /// </summary>
+    /// <typeparam name="T">Type to register.</typeparam>
+    /// <typeparam name="TSerializer">Serializer implementation used for 
<typeparamref name="T"/>.</typeparam>
+    /// <param name="typeNamespace">Namespace used on the wire.</param>
+    /// <param name="typeName">Type name used on the wire.</param>
+    /// <returns>The same runtime instance.</returns>
     public ThreadSafeFory Register<T, TSerializer>(string typeNamespace, 
string typeName)
         where TSerializer : Serializer<T>, new()
     {
@@ -70,46 +107,53 @@ public sealed class ThreadSafeFory : IDisposable
         return this;
     }
 
+    /// <summary>
+    /// Serializes a value into a new byte array containing one Fory frame.
+    /// </summary>
+    /// <typeparam name="T">Value type.</typeparam>
+    /// <param name="value">Value to serialize.</param>
+    /// <returns>Serialized bytes.</returns>
     public byte[] Serialize<T>(in T value)
     {
         return Current.Serialize(in value);
     }
 
+    /// <summary>
+    /// Serializes a value and writes one Fory frame into the provided buffer 
writer.
+    /// </summary>
+    /// <typeparam name="T">Value type.</typeparam>
+    /// <param name="output">Destination writer.</param>
+    /// <param name="value">Value to serialize.</param>
     public void Serialize<T>(IBufferWriter<byte> output, in T value)
     {
         Current.Serialize(output, in value);
     }
 
+    /// <summary>
+    /// Deserializes a value from one Fory frame in the provided span.
+    /// </summary>
+    /// <typeparam name="T">Target type.</typeparam>
+    /// <param name="payload">Serialized bytes containing exactly one 
frame.</param>
+    /// <returns>Deserialized value.</returns>
     public T Deserialize<T>(ReadOnlySpan<byte> payload)
     {
         return Current.Deserialize<T>(payload);
     }
 
+    /// <summary>
+    /// Deserializes a value from the head of a framed sequence and advances 
the sequence.
+    /// </summary>
+    /// <typeparam name="T">Target type.</typeparam>
+    /// <param name="payload">Input sequence. On success, sliced past the 
consumed frame.</param>
+    /// <returns>Deserialized value.</returns>
     public T Deserialize<T>(ref ReadOnlySequence<byte> payload)
     {
         return Current.Deserialize<T>(ref payload);
     }
 
-    public byte[] SerializeObject(object? value)
-    {
-        return Current.SerializeObject(value);
-    }
-
-    public void SerializeObject(IBufferWriter<byte> output, object? value)
-    {
-        Current.SerializeObject(output, value);
-    }
-
-    public object? DeserializeObject(ReadOnlySpan<byte> payload)
-    {
-        return Current.DeserializeObject(payload);
-    }
-
-    public object? DeserializeObject(ref ReadOnlySequence<byte> payload)
-    {
-        return Current.DeserializeObject(ref payload);
-    }
-
+    /// <summary>
+    /// Disposes thread-local runtimes and prevents further API use.
+    /// </summary>
     public void Dispose()
     {
         lock (_registrationLock)
diff --git a/csharp/src/Fory/TypeResolver.cs b/csharp/src/Fory/TypeResolver.cs
index 2f8b8ee72..c938c3052 100644
--- a/csharp/src/Fory/TypeResolver.cs
+++ b/csharp/src/Fory/TypeResolver.cs
@@ -293,42 +293,42 @@ public sealed class TypeResolver
         {
             case TypeId.CompatibleStruct:
             case TypeId.NamedCompatibleStruct:
-            {
-                TypeMeta typeMeta = BuildCompatibleTypeMeta(info, wireTypeId, 
context.TrackRef);
-                context.WriteCompatibleTypeMeta(type, typeMeta);
-                return;
-            }
-            case TypeId.NamedEnum:
-            case TypeId.NamedStruct:
-            case TypeId.NamedExt:
-            case TypeId.NamedUnion:
-            {
-                if (context.Compatible)
                 {
                     TypeMeta typeMeta = BuildCompatibleTypeMeta(info, 
wireTypeId, context.TrackRef);
                     context.WriteCompatibleTypeMeta(type, typeMeta);
+                    return;
                 }
-                else
+            case TypeId.NamedEnum:
+            case TypeId.NamedStruct:
+            case TypeId.NamedExt:
+            case TypeId.NamedUnion:
                 {
-                    if (!info.NamespaceName.HasValue || 
!info.TypeName.HasValue)
+                    if (context.Compatible)
                     {
-                        throw new InvalidDataException("missing type name 
metadata for name-registered type");
+                        TypeMeta typeMeta = BuildCompatibleTypeMeta(info, 
wireTypeId, context.TrackRef);
+                        context.WriteCompatibleTypeMeta(type, typeMeta);
+                    }
+                    else
+                    {
+                        if (!info.NamespaceName.HasValue || 
!info.TypeName.HasValue)
+                        {
+                            throw new InvalidDataException("missing type name 
metadata for name-registered type");
+                        }
+
+                        WriteMetaString(
+                            context,
+                            info.NamespaceName.Value,
+                            TypeMetaEncodings.NamespaceMetaStringEncodings,
+                            MetaStringEncoder.Namespace);
+                        WriteMetaString(
+                            context,
+                            info.TypeName.Value,
+                            TypeMetaEncodings.TypeNameMetaStringEncodings,
+                            MetaStringEncoder.TypeName);
                     }
 
-                    WriteMetaString(
-                        context,
-                        info.NamespaceName.Value,
-                        TypeMetaEncodings.NamespaceMetaStringEncodings,
-                        MetaStringEncoder.Namespace);
-                    WriteMetaString(
-                        context,
-                        info.TypeName.Value,
-                        TypeMetaEncodings.TypeNameMetaStringEncodings,
-                        MetaStringEncoder.TypeName);
+                    return;
                 }
-
-                return;
-            }
             default:
                 if (!info.RegisterByName && 
WireTypeNeedsUserTypeId(wireTypeId))
                 {
@@ -412,50 +412,50 @@ public sealed class TypeResolver
         {
             case TypeId.CompatibleStruct:
             case TypeId.NamedCompatibleStruct:
-            {
-                TypeMeta remoteTypeMeta = context.ReadCompatibleTypeMeta();
-                ValidateCompatibleTypeMeta(remoteTypeMeta, info, allowed, 
typeId);
-                context.PushCompatibleTypeMeta(type, remoteTypeMeta);
-                return;
-            }
+                {
+                    TypeMeta remoteTypeMeta = context.ReadCompatibleTypeMeta();
+                    ValidateCompatibleTypeMeta(remoteTypeMeta, info, allowed, 
typeId);
+                    context.PushCompatibleTypeMeta(type, remoteTypeMeta);
+                    return;
+                }
             case TypeId.NamedEnum:
             case TypeId.NamedStruct:
             case TypeId.NamedExt:
             case TypeId.NamedUnion:
-            {
-                if (context.Compatible)
                 {
-                    TypeMeta remoteTypeMeta = context.ReadCompatibleTypeMeta();
-                    ValidateCompatibleTypeMeta(remoteTypeMeta, info, allowed, 
typeId);
-                    if (typeId == TypeId.NamedStruct)
+                    if (context.Compatible)
                     {
-                        context.PushCompatibleTypeMeta(type, remoteTypeMeta);
+                        TypeMeta remoteTypeMeta = 
context.ReadCompatibleTypeMeta();
+                        ValidateCompatibleTypeMeta(remoteTypeMeta, info, 
allowed, typeId);
+                        if (typeId == TypeId.NamedStruct)
+                        {
+                            context.PushCompatibleTypeMeta(type, 
remoteTypeMeta);
+                        }
                     }
-                }
-                else
-                {
-                    MetaString namespaceName = ReadMetaString(
-                        context,
-                        MetaStringDecoder.Namespace,
-                        TypeMetaEncodings.NamespaceMetaStringEncodings);
-                    MetaString typeName = ReadMetaString(
-                        context,
-                        MetaStringDecoder.TypeName,
-                        TypeMetaEncodings.TypeNameMetaStringEncodings);
-                    if (!info.RegisterByName || !info.NamespaceName.HasValue 
|| !info.TypeName.HasValue)
+                    else
                     {
-                        throw new InvalidDataException("received 
name-registered type info for id-registered local type");
+                        MetaString namespaceName = ReadMetaString(
+                            context,
+                            MetaStringDecoder.Namespace,
+                            TypeMetaEncodings.NamespaceMetaStringEncodings);
+                        MetaString typeName = ReadMetaString(
+                            context,
+                            MetaStringDecoder.TypeName,
+                            TypeMetaEncodings.TypeNameMetaStringEncodings);
+                        if (!info.RegisterByName || 
!info.NamespaceName.HasValue || !info.TypeName.HasValue)
+                        {
+                            throw new InvalidDataException("received 
name-registered type info for id-registered local type");
+                        }
+
+                        if (namespaceName.Value != 
info.NamespaceName.Value.Value || typeName.Value != info.TypeName.Value.Value)
+                        {
+                            throw new InvalidDataException(
+                                $"type name mismatch: expected 
{info.NamespaceName.Value.Value}::{info.TypeName.Value.Value}, got 
{namespaceName.Value}::{typeName.Value}");
+                        }
                     }
 
-                    if (namespaceName.Value != info.NamespaceName.Value.Value 
|| typeName.Value != info.TypeName.Value.Value)
-                    {
-                        throw new InvalidDataException(
-                            $"type name mismatch: expected 
{info.NamespaceName.Value.Value}::{info.TypeName.Value.Value}, got 
{namespaceName.Value}::{typeName.Value}");
-                    }
+                    return;
                 }
-
-                return;
-            }
             default:
                 if (!info.RegisterByName && WireTypeNeedsUserTypeId(typeId))
                 {
@@ -553,31 +553,31 @@ public sealed class TypeResolver
         {
             case TypeId.CompatibleStruct:
             case TypeId.NamedCompatibleStruct:
-            {
-                TypeMeta typeMeta = context.ReadCompatibleTypeMeta();
-                if (typeMeta.RegisterByName)
                 {
-                    return new DynamicTypeInfo(wireTypeId, null, 
typeMeta.NamespaceName, typeMeta.TypeName, typeMeta);
-                }
+                    TypeMeta typeMeta = context.ReadCompatibleTypeMeta();
+                    if (typeMeta.RegisterByName)
+                    {
+                        return new DynamicTypeInfo(wireTypeId, null, 
typeMeta.NamespaceName, typeMeta.TypeName, typeMeta);
+                    }
 
-                return new DynamicTypeInfo(wireTypeId, typeMeta.UserTypeId, 
null, null, typeMeta);
-            }
+                    return new DynamicTypeInfo(wireTypeId, 
typeMeta.UserTypeId, null, null, typeMeta);
+                }
             case TypeId.NamedStruct:
             case TypeId.NamedEnum:
             case TypeId.NamedExt:
             case TypeId.NamedUnion:
-            {
-                MetaString namespaceName = ReadMetaString(context.Reader, 
MetaStringDecoder.Namespace, TypeMetaEncodings.NamespaceMetaStringEncodings);
-                MetaString typeName = ReadMetaString(context.Reader, 
MetaStringDecoder.TypeName, TypeMetaEncodings.TypeNameMetaStringEncodings);
-                return new DynamicTypeInfo(wireTypeId, null, namespaceName, 
typeName, null);
-            }
+                {
+                    MetaString namespaceName = ReadMetaString(context.Reader, 
MetaStringDecoder.Namespace, TypeMetaEncodings.NamespaceMetaStringEncodings);
+                    MetaString typeName = ReadMetaString(context.Reader, 
MetaStringDecoder.TypeName, TypeMetaEncodings.TypeNameMetaStringEncodings);
+                    return new DynamicTypeInfo(wireTypeId, null, 
namespaceName, typeName, null);
+                }
             case TypeId.Struct:
             case TypeId.Enum:
             case TypeId.Ext:
             case TypeId.TypedUnion:
-            {
-                return new DynamicTypeInfo(wireTypeId, 
context.Reader.ReadVarUInt32(), null, null, null);
-            }
+                {
+                    return new DynamicTypeInfo(wireTypeId, 
context.Reader.ReadVarUInt32(), null, null, null);
+                }
             default:
                 return new DynamicTypeInfo(wireTypeId, null, null, null, null);
         }
@@ -664,51 +664,51 @@ public sealed class TypeResolver
             case TypeId.Enum:
             case TypeId.Ext:
             case TypeId.TypedUnion:
-            {
-                if (!typeInfo.UserTypeId.HasValue)
                 {
-                    throw new InvalidDataException($"missing dynamic user type 
id for {typeInfo.WireTypeId}");
-                }
+                    if (!typeInfo.UserTypeId.HasValue)
+                    {
+                        throw new InvalidDataException($"missing dynamic user 
type id for {typeInfo.WireTypeId}");
+                    }
 
-                return ReadByUserTypeId(typeInfo.UserTypeId.Value, context);
-            }
+                    return ReadByUserTypeId(typeInfo.UserTypeId.Value, 
context);
+                }
             case TypeId.NamedStruct:
             case TypeId.NamedEnum:
             case TypeId.NamedExt:
             case TypeId.NamedUnion:
-            {
-                if (!typeInfo.NamespaceName.HasValue || 
!typeInfo.TypeName.HasValue)
                 {
-                    throw new InvalidDataException($"missing dynamic type name 
for {typeInfo.WireTypeId}");
-                }
+                    if (!typeInfo.NamespaceName.HasValue || 
!typeInfo.TypeName.HasValue)
+                    {
+                        throw new InvalidDataException($"missing dynamic type 
name for {typeInfo.WireTypeId}");
+                    }
 
-                return ReadByTypeName(typeInfo.NamespaceName.Value.Value, 
typeInfo.TypeName.Value.Value, context);
-            }
+                    return ReadByTypeName(typeInfo.NamespaceName.Value.Value, 
typeInfo.TypeName.Value.Value, context);
+                }
             case TypeId.CompatibleStruct:
             case TypeId.NamedCompatibleStruct:
-            {
-                if (typeInfo.CompatibleTypeMeta is null)
                 {
-                    throw new InvalidDataException($"missing compatible type 
meta for {typeInfo.WireTypeId}");
-                }
+                    if (typeInfo.CompatibleTypeMeta is null)
+                    {
+                        throw new InvalidDataException($"missing compatible 
type meta for {typeInfo.WireTypeId}");
+                    }
 
-                TypeMeta compatibleTypeMeta = typeInfo.CompatibleTypeMeta;
-                if (compatibleTypeMeta.RegisterByName)
-                {
-                    return ReadByTypeName(
-                        compatibleTypeMeta.NamespaceName.Value,
-                        compatibleTypeMeta.TypeName.Value,
-                        context,
-                        compatibleTypeMeta);
-                }
+                    TypeMeta compatibleTypeMeta = typeInfo.CompatibleTypeMeta;
+                    if (compatibleTypeMeta.RegisterByName)
+                    {
+                        return ReadByTypeName(
+                            compatibleTypeMeta.NamespaceName.Value,
+                            compatibleTypeMeta.TypeName.Value,
+                            context,
+                            compatibleTypeMeta);
+                    }
 
-                if (!compatibleTypeMeta.UserTypeId.HasValue)
-                {
-                    throw new InvalidDataException("missing user type id in 
compatible dynamic type meta");
-                }
+                    if (!compatibleTypeMeta.UserTypeId.HasValue)
+                    {
+                        throw new InvalidDataException("missing user type id 
in compatible dynamic type meta");
+                    }
 
-                return ReadByUserTypeId(compatibleTypeMeta.UserTypeId.Value, 
context, compatibleTypeMeta);
-            }
+                    return 
ReadByUserTypeId(compatibleTypeMeta.UserTypeId.Value, context, 
compatibleTypeMeta);
+                }
             case TypeId.None:
                 return null;
             default:
diff --git a/csharp/tests/Fory.Tests/ForyRuntimeTests.cs 
b/csharp/tests/Fory.Tests/ForyRuntimeTests.cs
index 5a9e4e6c2..ccc3ccafb 100644
--- a/csharp/tests/Fory.Tests/ForyRuntimeTests.cs
+++ b/csharp/tests/Fory.Tests/ForyRuntimeTests.cs
@@ -513,19 +513,19 @@ public sealed class ForyRuntimeTests
     }
 
     [Fact]
-    public void StreamDeserializeObjectConsumesSingleFrame()
+    public void StreamDeserializeGenericObjectConsumesSingleFrame()
     {
         ForyRuntime fory = ForyRuntime.Builder().Build();
 
-        byte[] p1 = fory.SerializeObject("first");
-        byte[] p2 = fory.SerializeObject(99);
+        byte[] p1 = fory.Serialize<object?>("first");
+        byte[] p2 = fory.Serialize<object?>(99);
         byte[] joined = new byte[p1.Length + p2.Length];
         Buffer.BlockCopy(p1, 0, joined, 0, p1.Length);
         Buffer.BlockCopy(p2, 0, joined, p1.Length, p2.Length);
 
         ReadOnlySequence<byte> sequence = new(joined);
-        object? first = fory.DeserializeObject(ref sequence);
-        object? second = fory.DeserializeObject(ref sequence);
+        object? first = fory.Deserialize<object?>(ref sequence);
+        object? second = fory.Deserialize<object?>(ref sequence);
 
         Assert.Equal("first", first);
         Assert.Equal(99, second);
@@ -790,7 +790,7 @@ public sealed class ForyRuntimeTests
             [true] = null,
         };
         Dictionary<object, object?> mapDecoded =
-            Assert.IsType<Dictionary<object, 
object?>>(fory.DeserializeObject(fory.SerializeObject(map)));
+            Assert.IsType<Dictionary<object, 
object?>>(fory.Deserialize<object?>(fory.Serialize<object?>(map)));
         Assert.Equal(3, mapDecoded.Count);
         Assert.Equal(7, mapDecoded["k1"]);
         Assert.Equal("v2", mapDecoded[2]);
@@ -799,7 +799,7 @@ public sealed class ForyRuntimeTests
 
         HashSet<object> set = ["a", 7, false];
         HashSet<object?> setDecoded =
-            
Assert.IsType<HashSet<object?>>(fory.DeserializeObject(fory.SerializeObject(set)));
+            
Assert.IsType<HashSet<object?>>(fory.Deserialize<object?>(fory.Serialize<object?>(set)));
         Assert.Equal(3, setDecoded.Count);
         Assert.Contains("a", setDecoded);
         Assert.Contains(7, setDecoded);
@@ -815,27 +815,52 @@ public sealed class ForyRuntimeTests
         queue.Enqueue("q1");
         queue.Enqueue(7);
         queue.Enqueue(null);
-        List<object?> queueDecoded = 
Assert.IsType<List<object?>>(fory.DeserializeObject(fory.SerializeObject(queue)));
+        List<object?> queueDecoded = 
Assert.IsType<List<object?>>(fory.Deserialize<object?>(fory.Serialize<object?>(queue)));
         Assert.Equal(new object?[] { "q1", 7, null }, queueDecoded.ToArray());
 
         Stack<object?> stack = new();
         stack.Push("s1");
         stack.Push(9);
-        List<object?> stackDecoded = 
Assert.IsType<List<object?>>(fory.DeserializeObject(fory.SerializeObject(stack)));
+        List<object?> stackDecoded = 
Assert.IsType<List<object?>>(fory.Deserialize<object?>(fory.Serialize<object?>(stack)));
         Assert.Equal(new object?[] { 9, "s1" }, stackDecoded.ToArray());
 
         LinkedList<object?> linkedList = new(new object?[] { "l1", 3, null });
-        List<object?> linkedListDecoded = 
Assert.IsType<List<object?>>(fory.DeserializeObject(fory.SerializeObject(linkedList)));
+        List<object?> linkedListDecoded = 
Assert.IsType<List<object?>>(fory.Deserialize<object?>(fory.Serialize<object?>(linkedList)));
         Assert.Equal(new object?[] { "l1", 3, null }, 
linkedListDecoded.ToArray());
 
         ImmutableHashSet<object?> immutableSet = 
ImmutableHashSet.Create<object?>("i1", 5);
         HashSet<object?> immutableSetDecoded =
-            
Assert.IsType<HashSet<object?>>(fory.DeserializeObject(fory.SerializeObject(immutableSet)));
+            
Assert.IsType<HashSet<object?>>(fory.Deserialize<object?>(fory.Serialize<object?>(immutableSet)));
         Assert.Equal(2, immutableSetDecoded.Count);
         Assert.Contains("i1", immutableSetDecoded);
         Assert.Contains(5, immutableSetDecoded);
     }
 
+    [Fact]
+    public void DynamicObjectReadDepthExceededThrows()
+    {
+        ForyRuntime writer = ForyRuntime.Builder().Build();
+        object? value = new List<object?> { new List<object?> { 1 } };
+        byte[] payload = writer.Serialize<object?>(value);
+
+        ForyRuntime reader = ForyRuntime.Builder().MaxDepth(2).Build();
+        InvalidDataException ex = Assert.Throws<InvalidDataException>(() => 
reader.Deserialize<object?>(payload));
+        Assert.Contains("dynamic object nesting depth", ex.Message);
+    }
+
+    [Fact]
+    public void DynamicObjectReadDepthWithinLimitRoundTrip()
+    {
+        ForyRuntime fory = ForyRuntime.Builder().MaxDepth(3).Build();
+        object? value = new List<object?> { new List<object?> { 1 } };
+
+        List<object?> outer = 
Assert.IsType<List<object?>>(fory.Deserialize<object?>(fory.Serialize<object?>(value)));
+        Assert.Single(outer);
+        List<object?> inner = Assert.IsType<List<object?>>(outer[0]);
+        Assert.Single(inner);
+        Assert.Equal(1, inner[0]);
+    }
+
     [Fact]
     public void GeneratedSerializerSupportsObjectKeyMap()
     {
diff --git a/csharp/tests/Fory.XlangPeer/Program.cs 
b/csharp/tests/Fory.XlangPeer/Program.cs
index 60fece54b..3c76bb7f1 100644
--- a/csharp/tests/Fory.XlangPeer/Program.cs
+++ b/csharp/tests/Fory.XlangPeer/Program.cs
@@ -339,7 +339,7 @@ internal static class Program
         List<byte> output = [];
         foreach (string sample in StringSamples)
         {
-            Append(output, fory.SerializeObject(sample));
+            Append(output, fory.Serialize<object?>(sample));
         }
 
         return output.ToArray();
@@ -389,33 +389,33 @@ internal static class Program
         Ensure(color == Color.White, "color mismatch");
 
         List<byte> output = [];
-        Append(output, fory.SerializeObject(b1));
-        Append(output, fory.SerializeObject(b2));
-        Append(output, fory.SerializeObject(i32));
-        Append(output, fory.SerializeObject(i8a));
-        Append(output, fory.SerializeObject(i8b));
-        Append(output, fory.SerializeObject(i16a));
-        Append(output, fory.SerializeObject(i16b));
-        Append(output, fory.SerializeObject(i32a));
-        Append(output, fory.SerializeObject(i32b));
-        Append(output, fory.SerializeObject(i64a));
-        Append(output, fory.SerializeObject(i64b));
-        Append(output, fory.SerializeObject(f32));
-        Append(output, fory.SerializeObject(f64));
-        Append(output, fory.SerializeObject(str));
-        Append(output, fory.SerializeObject(day));
-        Append(output, fory.SerializeObject(timestamp));
-        Append(output, fory.SerializeObject(bools));
-        Append(output, fory.SerializeObject(bytes));
-        Append(output, fory.SerializeObject(int16s));
-        Append(output, fory.SerializeObject(int32s));
-        Append(output, fory.SerializeObject(int64s));
-        Append(output, fory.SerializeObject(floats));
-        Append(output, fory.SerializeObject(doubles));
-        Append(output, fory.SerializeObject(list));
-        Append(output, fory.SerializeObject(set));
-        Append(output, fory.SerializeObject(map));
-        Append(output, fory.SerializeObject(color));
+        Append(output, fory.Serialize<object?>(b1));
+        Append(output, fory.Serialize<object?>(b2));
+        Append(output, fory.Serialize<object?>(i32));
+        Append(output, fory.Serialize<object?>(i8a));
+        Append(output, fory.Serialize<object?>(i8b));
+        Append(output, fory.Serialize<object?>(i16a));
+        Append(output, fory.Serialize<object?>(i16b));
+        Append(output, fory.Serialize<object?>(i32a));
+        Append(output, fory.Serialize<object?>(i32b));
+        Append(output, fory.Serialize<object?>(i64a));
+        Append(output, fory.Serialize<object?>(i64b));
+        Append(output, fory.Serialize<object?>(f32));
+        Append(output, fory.Serialize<object?>(f64));
+        Append(output, fory.Serialize<object?>(str));
+        Append(output, fory.Serialize<object?>(day));
+        Append(output, fory.Serialize<object?>(timestamp));
+        Append(output, fory.Serialize<object?>(bools));
+        Append(output, fory.Serialize<object?>(bytes));
+        Append(output, fory.Serialize<object?>(int16s));
+        Append(output, fory.Serialize<object?>(int32s));
+        Append(output, fory.Serialize<object?>(int64s));
+        Append(output, fory.Serialize<object?>(floats));
+        Append(output, fory.Serialize<object?>(doubles));
+        Append(output, fory.Serialize<object?>(list));
+        Append(output, fory.Serialize<object?>(set));
+        Append(output, fory.Serialize<object?>(map));
+        Append(output, fory.Serialize<object?>(color));
         return output.ToArray();
     }
 
@@ -445,10 +445,10 @@ internal static class Program
         EnsureConsumed(sequence, nameof(CaseList));
 
         List<byte> output = [];
-        Append(output, fory.SerializeObject(strList));
-        Append(output, fory.SerializeObject(strList2));
-        Append(output, fory.SerializeObject(itemList));
-        Append(output, fory.SerializeObject(itemList2));
+        Append(output, fory.Serialize<object?>(strList));
+        Append(output, fory.Serialize<object?>(strList2));
+        Append(output, fory.Serialize<object?>(itemList));
+        Append(output, fory.Serialize<object?>(itemList2));
         return output.ToArray();
     }
 
@@ -462,8 +462,8 @@ internal static class Program
         EnsureConsumed(sequence, nameof(CaseMap));
 
         List<byte> output = [];
-        Append(output, fory.SerializeObject(strMap));
-        Append(output, fory.SerializeObject(itemMap));
+        Append(output, fory.Serialize<object?>(strMap));
+        Append(output, fory.Serialize<object?>(itemMap));
         return output.ToArray();
     }
 
@@ -485,13 +485,13 @@ internal static class Program
         Ensure(obj.F3 == 3 && obj.F4 == 4 && obj.F5 == 0 && obj.F6 == 0, 
"item1 boxed fields mismatch");
 
         List<byte> output = [];
-        Append(output, fory.SerializeObject(obj));
-        Append(output, fory.SerializeObject(f1));
-        Append(output, fory.SerializeObject(f2));
-        Append(output, fory.SerializeObject(f3));
-        Append(output, fory.SerializeObject(f4));
-        Append(output, fory.SerializeObject(f5));
-        Append(output, fory.SerializeObject(f6));
+        Append(output, fory.Serialize<object?>(obj));
+        Append(output, fory.Serialize<object?>(f1));
+        Append(output, fory.Serialize<object?>(f2));
+        Append(output, fory.Serialize<object?>(f3));
+        Append(output, fory.Serialize<object?>(f4));
+        Append(output, fory.Serialize<object?>(f5));
+        Append(output, fory.Serialize<object?>(f6));
         return output.ToArray();
     }
 
@@ -506,9 +506,9 @@ internal static class Program
         EnsureConsumed(sequence, nameof(CaseItem));
 
         List<byte> output = [];
-        Append(output, fory.SerializeObject(i1));
-        Append(output, fory.SerializeObject(i2));
-        Append(output, fory.SerializeObject(i3));
+        Append(output, fory.Serialize<object?>(i1));
+        Append(output, fory.Serialize<object?>(i2));
+        Append(output, fory.Serialize<object?>(i3));
         return output.ToArray();
     }
 
@@ -524,10 +524,10 @@ internal static class Program
         EnsureConsumed(sequence, nameof(CaseColor));
 
         List<byte> output = [];
-        Append(output, fory.SerializeObject(c1));
-        Append(output, fory.SerializeObject(c2));
-        Append(output, fory.SerializeObject(c3));
-        Append(output, fory.SerializeObject(c4));
+        Append(output, fory.Serialize<object?>(c1));
+        Append(output, fory.Serialize<object?>(c2));
+        Append(output, fory.Serialize<object?>(c3));
+        Append(output, fory.Serialize<object?>(c4));
         return output.ToArray();
     }
 
@@ -541,8 +541,8 @@ internal static class Program
         EnsureConsumed(sequence, nameof(CaseStructWithList));
 
         List<byte> output = [];
-        Append(output, fory.SerializeObject(s1));
-        Append(output, fory.SerializeObject(s2));
+        Append(output, fory.Serialize<object?>(s1));
+        Append(output, fory.Serialize<object?>(s2));
         return output.ToArray();
     }
 
@@ -556,8 +556,8 @@ internal static class Program
         EnsureConsumed(sequence, nameof(CaseStructWithMap));
 
         List<byte> output = [];
-        Append(output, fory.SerializeObject(s1));
-        Append(output, fory.SerializeObject(s2));
+        Append(output, fory.Serialize<object?>(s1));
+        Append(output, fory.Serialize<object?>(s2));
         return output.ToArray();
     }
 
@@ -577,8 +577,8 @@ internal static class Program
         Ensure(second.Union.Value is long secondValue && secondValue == 42L, 
"union case value mismatch for second value");
 
         List<byte> output = [];
-        Append(output, fory.SerializeObject(first));
-        Append(output, fory.SerializeObject(second));
+        Append(output, fory.Serialize<object?>(first));
+        Append(output, fory.Serialize<object?>(second));
         return output.ToArray();
     }
 
@@ -614,19 +614,19 @@ internal static class Program
         for (int i = 0; i < 3; i++)
         {
             Color color = fory.Deserialize<Color>(ref sequence);
-            Append(output, fory.SerializeObject(color));
+            Append(output, fory.Serialize<object?>(color));
         }
 
         for (int i = 0; i < 3; i++)
         {
             MyStruct myStruct = fory.Deserialize<MyStruct>(ref sequence);
-            Append(output, fory.SerializeObject(myStruct));
+            Append(output, fory.Serialize<object?>(myStruct));
         }
 
         for (int i = 0; i < 3; i++)
         {
             MyExt myExt = fory.Deserialize<MyExt>(ref sequence);
-            Append(output, fory.SerializeObject(myExt));
+            Append(output, fory.Serialize<object?>(myExt));
         }
 
         EnsureConsumed(sequence, nameof(CaseConsistentNamed));
@@ -653,8 +653,8 @@ internal static class Program
         EnsureConsumed(sequence, nameof(CasePolymorphicList));
 
         List<byte> output = [];
-        Append(output, fory.SerializeObject(animals));
-        Append(output, fory.SerializeObject(holder));
+        Append(output, fory.Serialize<object?>(animals));
+        Append(output, fory.Serialize<object?>(holder));
         return output.ToArray();
     }
 
@@ -671,8 +671,8 @@ internal static class Program
         EnsureConsumed(sequence, nameof(CasePolymorphicMap));
 
         List<byte> output = [];
-        Append(output, fory.SerializeObject(map));
-        Append(output, fory.SerializeObject(holder));
+        Append(output, fory.Serialize<object?>(map));
+        Append(output, fory.Serialize<object?>(holder));
         return output.ToArray();
     }
 
@@ -768,7 +768,7 @@ internal static class Program
         EnsureConsumed(sequence, 
nameof(CaseEnumSchemaEvolutionCompatibleReverse));
         Ensure(value.F1 == TestEnum.ValueC, "enum schema evolution reverse F1 
mismatch");
         Ensure(value.F2 == TestEnum.ValueA, "enum schema evolution reverse F2 
default mismatch");
-        return fory.SerializeObject(value);
+        return fory.Serialize<object?>(value);
     }
 
     private static byte[] CaseNullableFieldSchemaConsistentNotNull(byte[] 
input)
@@ -809,7 +809,7 @@ internal static class Program
         RefOuterSchemaConsistent outer = 
fory.Deserialize<RefOuterSchemaConsistent>(ref sequence);
         EnsureConsumed(sequence, nameof(CaseRefSchemaConsistent));
         Ensure(ReferenceEquals(outer.Inner1, outer.Inner2), "reference 
tracking mismatch");
-        return fory.SerializeObject(outer);
+        return fory.Serialize<object?>(outer);
     }
 
     private static byte[] CaseRefCompatible(byte[] input)
@@ -822,7 +822,7 @@ internal static class Program
         RefOuterCompatible outer = fory.Deserialize<RefOuterCompatible>(ref 
sequence);
         EnsureConsumed(sequence, nameof(CaseRefCompatible));
         Ensure(ReferenceEquals(outer.Inner1, outer.Inner2), "reference 
tracking mismatch");
-        return fory.SerializeObject(outer);
+        return fory.Serialize<object?>(outer);
     }
 
     private static byte[] CaseCollectionElementRefOverride(byte[] input)
@@ -857,7 +857,7 @@ internal static class Program
             }
         }
 
-        return fory.SerializeObject(container);
+        return fory.Serialize<object?>(container);
     }
 
     private static byte[] CaseCircularRefSchemaConsistent(byte[] input)
@@ -869,7 +869,7 @@ internal static class Program
         CircularRefStruct value = fory.Deserialize<CircularRefStruct>(ref 
sequence);
         EnsureConsumed(sequence, nameof(CaseCircularRefSchemaConsistent));
         Ensure(ReferenceEquals(value, value.SelfRef), "circular ref mismatch");
-        return fory.SerializeObject(value);
+        return fory.Serialize<object?>(value);
     }
 
     private static byte[] CaseCircularRefCompatible(byte[] input)
@@ -881,7 +881,7 @@ internal static class Program
         CircularRefStruct value = fory.Deserialize<CircularRefStruct>(ref 
sequence);
         EnsureConsumed(sequence, nameof(CaseCircularRefCompatible));
         Ensure(ReferenceEquals(value, value.SelfRef), "circular ref mismatch");
-        return fory.SerializeObject(value);
+        return fory.Serialize<object?>(value);
     }
 
     private static byte[] CaseUnsignedSchemaConsistentSimple(byte[] input)
@@ -910,7 +910,7 @@ internal static class Program
         ReadOnlySequence<byte> sequence = new(input);
         T value = fory.Deserialize<T>(ref sequence);
         EnsureConsumed(sequence, typeof(T).Name);
-        return fory.SerializeObject(value);
+        return fory.Serialize<object?>(value);
     }
 
     private static void RegisterSimpleById(ForyRuntime fory)
diff --git a/docs/guide/csharp/basic-serialization.md 
b/docs/guide/csharp/basic-serialization.md
index 2188c912d..9c4573a0a 100644
--- a/docs/guide/csharp/basic-serialization.md
+++ b/docs/guide/csharp/basic-serialization.md
@@ -19,7 +19,7 @@ license: |
   limitations under the License.
 ---
 
-This page covers typed and dynamic serialization APIs in Apache Fory™ C#.
+This page covers typed serialization APIs in Apache Fory™ C#.
 
 ## Object Graph Serialization
 
@@ -88,9 +88,9 @@ MyType first = fory.Deserialize<MyType>(ref sequence);
 MyType second = fory.Deserialize<MyType>(ref sequence);
 ```
 
-## Dynamic Object API
+## Dynamic Payloads via Generic Object API
 
-Use object APIs when the compile-time type is unknown or heterogeneous.
+When the compile-time type is unknown or heterogeneous, use the generic API 
with `object?`.
 
 ```csharp
 Dictionary<object, object?> value = new()
@@ -100,8 +100,8 @@ Dictionary<object, object?> value = new()
     [true] = null,
 };
 
-byte[] payload = fory.SerializeObject(value);
-object? decoded = fory.DeserializeObject(payload);
+byte[] payload = fory.Serialize<object?>(value);
+object? decoded = fory.Deserialize<object?>(payload);
 ```
 
 ## Buffer Writer API
@@ -115,7 +115,7 @@ ArrayBufferWriter<byte> writer = new();
 fory.Serialize(writer, value);
 
 ArrayBufferWriter<byte> dynamicWriter = new();
-fory.SerializeObject(dynamicWriter, value);
+fory.Serialize<object?>(dynamicWriter, value);
 ```
 
 ## Notes
diff --git a/docs/guide/csharp/index.md b/docs/guide/csharp/index.md
index c12c5a32e..b18e414e2 100644
--- a/docs/guide/csharp/index.md
+++ b/docs/guide/csharp/index.md
@@ -19,7 +19,7 @@ license: |
   limitations under the License.
 ---
 
-Apache Fory™ C# is a high-performance, cross-language serialization runtime 
for .NET. It provides object graph serialization, schema evolution, dynamic 
object support, and a thread-safe wrapper for concurrent workloads.
+Apache Fory™ C# is a high-performance, cross-language serialization runtime 
for .NET. It provides object graph serialization, schema evolution, generic 
object payload support, and a thread-safe wrapper for concurrent workloads.
 
 ## Why Fory C#?
 
@@ -67,7 +67,7 @@ User decoded = fory.Deserialize<User>(payload);
 ## Core API Surface
 
 - `Serialize<T>(in T value)` / `Deserialize<T>(...)`
-- `SerializeObject(object? value)` / `DeserializeObject(...)` for dynamic 
payloads
+- `Serialize<object?>(...)` / `Deserialize<object?>(...)` for dynamic payloads
 - `Register<T>(uint typeId)` and namespace/name registration APIs
 - `Register<T, TSerializer>(...)` for custom serializers
 
diff --git a/docs/guide/csharp/supported-types.md 
b/docs/guide/csharp/supported-types.md
index 8a89a3fab..0b6397788 100644
--- a/docs/guide/csharp/supported-types.md
+++ b/docs/guide/csharp/supported-types.md
@@ -79,7 +79,7 @@ This page summarizes built-in and generated type support in 
Apache Fory™ C#.
 
 ## Dynamic Types
 
-Dynamic object APIs (`SerializeObject` / `DeserializeObject`) support:
+Dynamic object payloads via `Serialize<object?>` / `Deserialize<object?>` 
support:
 
 - Primitive/object values
 - Dynamic lists/sets/maps
diff --git a/python/setup.py b/python/setup.py
index 6dc32ade8..2a8457d00 100644
--- a/python/setup.py
+++ b/python/setup.py
@@ -18,6 +18,9 @@
 import os
 import platform
 import subprocess
+import sys
+import threading
+import time
 from os.path import abspath, join as pjoin
 
 from setuptools import setup
@@ -40,6 +43,82 @@ print(f"setup_dir: {setup_dir}")
 print(f"project_dir: {project_dir}")
 print(f"fory_cpp_src_dir: {fory_cpp_src_dir}")
 
+_RETRYABLE_NETWORK_ERROR_PATTERNS = (
+    "error downloading",
+    "download_and_extract",
+    "download from",
+    "http archive",
+    "get returned 500",
+    "get returned 502",
+    "get returned 503",
+    "get returned 504",
+    "network is unreachable",
+    "connection refused",
+    "connection reset",
+    "connection timed out",
+    "timed out waiting for",
+    "timed out",
+    "name resolution",
+    "temporary failure in name resolution",
+    "tls handshake timeout",
+    "temporary failure",
+)
+
+
+def _is_retryable_network_error(output: str) -> bool:
+    lowered = output.lower()
+    return any(pattern in lowered for pattern in 
_RETRYABLE_NETWORK_ERROR_PATTERNS)
+
+
+def _stream_pipe(pipe, sink, chunks):
+    try:
+        for line in iter(pipe.readline, ""):
+            chunks.append(line)
+            sink.write(line)
+            sink.flush()
+    finally:
+        pipe.close()
+
+
+def _run_with_retry(args, cwd, max_attempts=3):
+    for attempt in range(1, max_attempts + 1):
+        process = subprocess.Popen(
+            args,
+            cwd=cwd,
+            stdout=subprocess.PIPE,
+            stderr=subprocess.PIPE,
+            text=True,
+            errors="replace",
+            bufsize=1,
+        )
+
+        stdout_chunks = []
+        stderr_chunks = []
+        stdout_thread = threading.Thread(target=_stream_pipe, 
args=(process.stdout, sys.stdout, stdout_chunks))
+        stderr_thread = threading.Thread(target=_stream_pipe, 
args=(process.stderr, sys.stderr, stderr_chunks))
+        stdout_thread.start()
+        stderr_thread.start()
+        returncode = process.wait()
+        stdout_thread.join()
+        stderr_thread.join()
+
+        stdout_text = "".join(stdout_chunks)
+        stderr_text = "".join(stderr_chunks)
+        combined_output = f"{stdout_text}\n{stderr_text}"
+        if returncode == 0:
+            return
+
+        if attempt >= max_attempts or not 
_is_retryable_network_error(combined_output):
+            raise subprocess.CalledProcessError(returncode, args, 
output=stdout_text, stderr=stderr_text)
+
+        backoff_seconds = attempt * 5
+        print(
+            f"Detected transient network/download error while running {' 
'.join(args)} "
+            f"(attempt {attempt}/{max_attempts}); retrying in 
{backoff_seconds}s.",
+            file=sys.stderr,
+        )
+        time.sleep(backoff_seconds)
+
 
 class BinaryDistribution(Distribution):
     def __init__(self, attrs=None):
@@ -59,7 +138,7 @@ class BinaryDistribution(Distribution):
             bazel_args += ["//:cp_fory_so"]
             # Ensure Windows path compatibility
             cwd_path = os.path.normpath(project_dir)
-            subprocess.check_call(bazel_args, cwd=cwd_path)
+            _run_with_retry(bazel_args, cwd=cwd_path)
 
     def has_ext_modules(self):
         return True


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


Reply via email to