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 98e20c397 refactor(csharp): split Fory object attributes (#3709)
98e20c397 is described below

commit 98e20c397770e7551be6d239c2343b440312db2d
Author: Shawn Yang <[email protected]>
AuthorDate: Wed May 27 15:22:40 2026 +0800

    refactor(csharp): split Fory object attributes (#3709)
    
    ## Why?
    
    
    
    ## What does this PR do?
    
    
    
    ## Related issues
    
    
    
    ## AI Contribution Checklist
    
    
    
    - [ ] Substantial AI assistance was used in this PR: `yes` / `no`
    - [ ] If `yes`, I included a completed [AI Contribution
    
Checklist](https://github.com/apache/fory/blob/main/AI_POLICY.md#9-contributor-checklist-for-ai-assisted-prs)
    in this PR description and the required `AI Usage Disclosure`.
    - [ ] If `yes`, my PR description includes the required `ai_review`
    summary and screenshot evidence of the final clean AI review results
    from both fresh reviewers on the current PR diff or current HEAD after
    the latest code changes.
    
    
    
    ## Does this PR introduce any user-facing change?
    
    
    
    - [ ] Does this PR introduce any public API change?
    - [ ] Does this PR introduce any binary protocol compatibility change?
    
    ## Benchmark
---
 README.md                                          |   2 +-
 benchmarks/csharp/BenchmarkModels.cs               |  20 +--
 compiler/README.md                                 |   4 +-
 compiler/fory_compiler/generators/csharp.py        |   7 +-
 .../fory_compiler/tests/test_csharp_generator.py   |  26 ++++
 csharp/README.md                                   |  18 +--
 .../Fory.Generator/AnalyzerReleases.Unshipped.md   |   3 +-
 ...oryObjectGenerator.cs => ForyModelGenerator.cs} | 138 +++++++++++++++++++--
 csharp/src/Fory/Attributes.cs                      |  22 +++-
 csharp/src/Fory/TypeInfo.cs                        |   2 +-
 csharp/tests/Fory.Tests/ForyGeneratorTests.cs      |  57 ++++++++-
 csharp/tests/Fory.Tests/ForyRuntimeTests.cs        |  76 ++++++------
 csharp/tests/Fory.Tests/RuntimeEdgeCaseTests.cs    |   8 +-
 csharp/tests/Fory.XlangPeer/Program.cs             |  86 ++++++-------
 docs/compiler/generated-code.md                    |   7 +-
 docs/compiler/index.md                             |   4 +-
 docs/compiler/schema-idl.md                        |   4 +-
 docs/guide/csharp/basic-serialization.md           |   8 +-
 docs/guide/csharp/custom-serializers.md            |   4 +-
 docs/guide/csharp/index.md                         |   6 +-
 docs/guide/csharp/references.md                    |   2 +-
 docs/guide/csharp/schema-evolution.md              |   4 +-
 docs/guide/csharp/schema-metadata.md               |   8 +-
 docs/guide/csharp/supported-types.md               |   2 +-
 docs/guide/csharp/xlang-serialization.md           |   2 +-
 docs/guide/dart/xlang-serialization.md             |   2 +-
 docs/guide/go/type-registration.md                 |   4 +-
 docs/guide/python/xlang-serialization.md           |   4 +-
 docs/specification/xlang_type_mapping.md           |  18 +--
 29 files changed, 379 insertions(+), 169 deletions(-)

diff --git a/README.md b/README.md
index 507891cad..729382467 100644
--- a/README.md
+++ b/README.md
@@ -472,7 +472,7 @@ console.log(person.name);
 ```csharp
 using Apache.Fory;
 
-[ForyObject]
+[ForyStruct]
 public sealed class Person
 {
     public string Name { get; set; } = string.Empty;
diff --git a/benchmarks/csharp/BenchmarkModels.cs 
b/benchmarks/csharp/BenchmarkModels.cs
index d4ee54e0f..a7058c994 100644
--- a/benchmarks/csharp/BenchmarkModels.cs
+++ b/benchmarks/csharp/BenchmarkModels.cs
@@ -21,7 +21,7 @@ using ProtoBuf;
 
 namespace Apache.Fory.Benchmarks.CSharp;
 
-[ForyObject]
+[ForyStruct]
 [MessagePackObject(keyAsPropertyName: true)]
 [ProtoContract]
 public sealed class NumericStruct
@@ -75,7 +75,7 @@ public sealed class NumericStruct
     public int F12 { get; set; }
 }
 
-[ForyObject]
+[ForyStruct]
 [MessagePackObject(keyAsPropertyName: true)]
 [ProtoContract]
 public sealed class NumericStructList
@@ -85,7 +85,7 @@ public sealed class NumericStructList
     public List<NumericStruct> Values { get; set; } = [];
 }
 
-[ForyObject]
+[ForyStruct]
 [MessagePackObject(keyAsPropertyName: true)]
 [ProtoContract]
 public sealed class Sample
@@ -179,7 +179,7 @@ public sealed class Sample
     public string String { get; set; } = string.Empty;
 }
 
-[ForyObject]
+[ForyStruct]
 [MessagePackObject(keyAsPropertyName: true)]
 [ProtoContract]
 public sealed class SampleList
@@ -189,7 +189,7 @@ public sealed class SampleList
     public List<Sample> Values { get; set; } = [];
 }
 
-[ForyObject]
+[ForyStruct]
 [ProtoContract]
 public enum Player
 {
@@ -199,7 +199,7 @@ public enum Player
     Flash,
 }
 
-[ForyObject]
+[ForyStruct]
 [ProtoContract]
 public enum MediaSize
 {
@@ -209,7 +209,7 @@ public enum MediaSize
     Large,
 }
 
-[ForyObject]
+[ForyStruct]
 [MessagePackObject(keyAsPropertyName: true)]
 [ProtoContract]
 public sealed class Media
@@ -263,7 +263,7 @@ public sealed class Media
     public string Copyright { get; set; } = string.Empty;
 }
 
-[ForyObject]
+[ForyStruct]
 [MessagePackObject(keyAsPropertyName: true)]
 [ProtoContract]
 public sealed class Image
@@ -289,7 +289,7 @@ public sealed class Image
     public MediaSize Size { get; set; }
 }
 
-[ForyObject]
+[ForyStruct]
 [MessagePackObject(keyAsPropertyName: true)]
 [ProtoContract]
 public sealed class MediaContent
@@ -303,7 +303,7 @@ public sealed class MediaContent
     public List<Image> Images { get; set; } = [];
 }
 
-[ForyObject]
+[ForyStruct]
 [MessagePackObject(keyAsPropertyName: true)]
 [ProtoContract]
 public sealed class MediaContentList
diff --git a/compiler/README.md b/compiler/README.md
index aece751cd..99c690b0c 100644
--- a/compiler/README.md
+++ b/compiler/README.md
@@ -407,12 +407,12 @@ struct Cat {
 
 Generates classes with:
 
-- `[ForyObject]` model attributes
+- `[ForyStruct]`, `[ForyEnum]`, and `[ForyUnion]` model attributes
 - Auto-properties for schema fields
 - Registration helper class and `ToBytes`/`FromBytes` helpers
 
 ```csharp
-[ForyObject]
+[ForyStruct]
 public sealed partial class Cat
 {
     public Dog? Friend { get; set; }
diff --git a/compiler/fory_compiler/generators/csharp.py 
b/compiler/fory_compiler/generators/csharp.py
index c5060d123..c9d1716fa 100644
--- a/compiler/fory_compiler/generators/csharp.py
+++ b/compiler/fory_compiler/generators/csharp.py
@@ -675,7 +675,7 @@ class CSharpGenerator(BaseGenerator):
         comment = self.format_type_id_comment(enum, f"{ind}//")
         if comment:
             lines.append(comment)
-        lines.append(f"{ind}[ForyObject]")
+        lines.append(f"{ind}[ForyEnum]")
         lines.append(f"{ind}public enum 
{self.safe_type_identifier(enum.name)}")
         lines.append(f"{ind}{{")
 
@@ -704,6 +704,7 @@ class CSharpGenerator(BaseGenerator):
         comment = self.format_type_id_comment(union, f"{ind}//")
         if comment:
             lines.append(comment)
+        lines.append(f"{ind}[ForyUnion]")
         lines.append(f"{ind}public sealed class {type_name} : Union")
         lines.append(f"{ind}{{")
         lines.append(f"{ind}{self.indent_str}public enum {case_enum}")
@@ -823,9 +824,9 @@ class CSharpGenerator(BaseGenerator):
         if comment:
             lines.append(comment)
         if self.get_effective_evolving(message):
-            lines.append(f"{ind}[ForyObject]")
+            lines.append(f"{ind}[ForyStruct]")
         else:
-            lines.append(f"{ind}[ForyObject(Evolving = false)]")
+            lines.append(f"{ind}[ForyStruct(Evolving = false)]")
         lines.append(f"{ind}public sealed partial class {type_name}")
         lines.append(f"{ind}{{")
 
diff --git a/compiler/fory_compiler/tests/test_csharp_generator.py 
b/compiler/fory_compiler/tests/test_csharp_generator.py
index f84d5d87d..0c1c78a9b 100644
--- a/compiler/fory_compiler/tests/test_csharp_generator.py
+++ b/compiler/fory_compiler/tests/test_csharp_generator.py
@@ -69,6 +69,32 @@ def test_csharp_namespace_fallback_to_package():
     assert "namespace com.example.models;" in file.content
 
 
+def test_csharp_semantic_model_attributes():
+    file = generate(
+        """
+        package example;
+
+        enum Status {
+            READY = 1;
+        }
+
+        union Choice {
+            string text = 1;
+        }
+
+        message Envelope {
+            Status status = 1;
+            Choice choice = 2;
+        }
+        """
+    )
+
+    assert "[ForyEnum]" in file.content
+    assert "[ForyUnion]" in file.content
+    assert "[ForyStruct]" in file.content
+    assert "[ForyObject]" not in file.content
+
+
 def test_csharp_registration_uses_fdl_package_for_name_registration():
     file = generate(
         """
diff --git a/csharp/README.md b/csharp/README.md
index 8b80b8f2b..20df35242 100644
--- a/csharp/README.md
+++ b/csharp/README.md
@@ -10,7 +10,7 @@ The C# implementation provides high-performance object graph 
serialization for .
 
 - High-performance binary serialization for .NET 8+
 - Cross-language compatibility with Java, Python, C++, Go, Rust, and JavaScript
-- Source-generator-based serializers for `[ForyObject]` types
+- Source-generator-based serializers for `[ForyStruct]` types, plus 
`[ForyEnum]` and `[ForyUnion]` registration
 - Field-level schema descriptors with `[ForyField(Type = typeof(...))]`
 - Optional shared/circular reference tracking (`TrackRef(true)`)
 - Compatible mode for schema evolution
@@ -27,7 +27,7 @@ The C# implementation provides high-performance object graph 
serialization for .
 
 ### Add Apache Fory™ C\#
 
-From NuGet, reference the single `Apache.Fory` package. It includes the 
runtime plus the source generator for `[ForyObject]` types.
+From NuGet, reference the single `Apache.Fory` package. It includes the 
runtime plus the source generator for `[ForyStruct]`, `[ForyEnum]`, and 
`[ForyUnion]` types.
 
 ```xml
 <ItemGroup>
@@ -52,7 +52,7 @@ For local development against this repository, reference the 
runtime project and
 ```csharp
 using Apache.Fory;
 
-[ForyObject]
+[ForyStruct]
 public sealed class User
 {
     public long Id { get; set; }
@@ -78,17 +78,17 @@ User decoded = fory.Deserialize<User>(payload);
 
 ### 1. Object Graph Serialization
 
-`[ForyObject]` types are serialized with generated serializers.
+`[ForyStruct]` types are serialized with generated serializers.
 
 ```csharp
-[ForyObject]
+[ForyStruct]
 public sealed class Address
 {
     public string Street { get; set; } = string.Empty;
     public int Zip { get; set; }
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class Person
 {
     public long Id { get; set; }
@@ -106,7 +106,7 @@ fory.Register<Person>(101);
 Enable reference tracking to preserve object identity.
 
 ```csharp
-[ForyObject]
+[ForyStruct]
 public sealed class Node
 {
     public int Value { get; set; }
@@ -128,13 +128,13 @@ 
System.Diagnostics.Debug.Assert(object.ReferenceEquals(decoded, decoded.Next));
 Compatible mode allows schema changes between writer and reader.
 
 ```csharp
-[ForyObject]
+[ForyStruct]
 public sealed class OneField
 {
     public string? F1 { get; set; }
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class TwoFields
 {
     public string F1 { get; set; } = string.Empty;
diff --git a/csharp/src/Fory.Generator/AnalyzerReleases.Unshipped.md 
b/csharp/src/Fory.Generator/AnalyzerReleases.Unshipped.md
index 8bec692b5..d67ff3684 100644
--- a/csharp/src/Fory.Generator/AnalyzerReleases.Unshipped.md
+++ b/csharp/src/Fory.Generator/AnalyzerReleases.Unshipped.md
@@ -2,7 +2,8 @@
 
 Rule ID | Category | Severity | Notes
 --------|----------|----------|------
-FORY001 | Fory | Error | Generic types are not supported by ForyObject 
generator
+FORY001 | Fory | Error | Generic types are not supported by the Fory source 
generator
 FORY002 | Fory | Error | Missing parameterless constructor
 FORY003 | Fory | Error | Unsupported Field encoding
 FORY004 | Fory | Error | Invalid Fory field id
+FORY005 | Fory | Error | Invalid Fory union type
diff --git a/csharp/src/Fory.Generator/ForyObjectGenerator.cs 
b/csharp/src/Fory.Generator/ForyModelGenerator.cs
similarity index 96%
rename from csharp/src/Fory.Generator/ForyObjectGenerator.cs
rename to csharp/src/Fory.Generator/ForyModelGenerator.cs
index cf320aac2..05f5faefb 100644
--- a/csharp/src/Fory.Generator/ForyObjectGenerator.cs
+++ b/csharp/src/Fory.Generator/ForyModelGenerator.cs
@@ -25,7 +25,7 @@ using Microsoft.CodeAnalysis.Text;
 namespace Apache.Fory.Generator;
 
 [Generator(LanguageNames.CSharp)]
-public sealed class ForyObjectGenerator : IIncrementalGenerator
+public sealed class ForyModelGenerator : IIncrementalGenerator
 {
     private static readonly SymbolDisplayFormat FullNameFormat =
         SymbolDisplayFormat.FullyQualifiedFormat.WithMiscellaneousOptions(
@@ -33,8 +33,8 @@ 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]",
+        title: "Generic types are not supported by the Fory source generator",
+        messageFormat: "Type '{0}' is generic and is not supported by 
generated Fory attributes",
         category: "Fory",
         defaultSeverity: DiagnosticSeverity.Error,
         isEnabledByDefault: true);
@@ -42,7 +42,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 [ForyStruct]",
         category: "Fory",
         defaultSeverity: DiagnosticSeverity.Error,
         isEnabledByDefault: true);
@@ -63,12 +63,19 @@ public sealed class ForyObjectGenerator : 
IIncrementalGenerator
         defaultSeverity: DiagnosticSeverity.Error,
         isEnabledByDefault: true);
 
+    private static readonly DiagnosticDescriptor InvalidUnionType = new(
+        id: "FORY005",
+        title: "Invalid Fory union type",
+        messageFormat: "Class '{0}' must derive from Apache.Fory.Union for 
[ForyUnion]",
+        category: "Fory",
+        defaultSeverity: DiagnosticSeverity.Error,
+        isEnabledByDefault: true);
+
     public void Initialize(IncrementalGeneratorInitializationContext context)
     {
         IncrementalValuesProvider<TypeModel?> typeModels = 
context.SyntaxProvider
-            .ForAttributeWithMetadataName(
-                "Apache.Fory.ForyObjectAttribute",
-                static (node, _) => node is TypeDeclarationSyntax || node is 
EnumDeclarationSyntax,
+            .CreateSyntaxProvider(
+                static (node, _) => HasCandidateAttributes(node),
                 static (syntaxContext, ct) => BuildTypeModel(syntaxContext, 
ct))
             .Where(static m => m is not null);
 
@@ -77,6 +84,16 @@ public sealed class ForyObjectGenerator : 
IIncrementalGenerator
             static (spc, models) => Emit(spc, models));
     }
 
+    private static bool HasCandidateAttributes(SyntaxNode node)
+    {
+        return node switch
+        {
+            TypeDeclarationSyntax typeDeclaration => 
typeDeclaration.AttributeLists.Count > 0,
+            EnumDeclarationSyntax enumDeclaration => 
enumDeclaration.AttributeLists.Count > 0,
+            _ => false,
+        };
+    }
+
     private static void Emit(SourceProductionContext context, 
ImmutableArray<TypeModel?> maybeModels)
     {
         if (maybeModels.IsDefaultOrEmpty)
@@ -139,6 +156,11 @@ public sealed class ForyObjectGenerator : 
IIncrementalGenerator
                 sb.AppendLine(
                     $"        
global::Apache.Fory.TypeResolver.RegisterGenerated<{model.TypeName}, 
global::Apache.Fory.EnumSerializer<{model.TypeName}>>();");
             }
+            else if (model.Kind == DeclKind.Union)
+            {
+                sb.AppendLine(
+                    $"        
global::Apache.Fory.TypeResolver.RegisterGenerated<{model.TypeName}, 
global::Apache.Fory.UnionSerializer<{model.TypeName}>>();");
+            }
             else
             {
                 sb.AppendLine(
@@ -2112,23 +2134,43 @@ public sealed class ForyObjectGenerator : 
IIncrementalGenerator
         return BoolLiteral(member.NeedsFieldTypeInfo);
     }
 
-    private static TypeModel? BuildTypeModel(GeneratorAttributeSyntaxContext 
context, CancellationToken cancellationToken)
+    private static TypeModel? BuildTypeModel(GeneratorSyntaxContext context, 
CancellationToken cancellationToken)
     {
         _ = cancellationToken;
-        if (context.TargetSymbol is not INamedTypeSymbol typeSymbol)
+        if (context.SemanticModel.GetDeclaredSymbol(context.Node, 
cancellationToken) is not INamedTypeSymbol typeSymbol)
         {
             return null;
         }
 
-        if (typeSymbol.TypeParameters.Length > 0)
+        ForyAttributeKind attributeKind = GetForyAttributeKind(typeSymbol);
+        if (attributeKind == ForyAttributeKind.None)
         {
             return null;
         }
 
         string typeName = typeSymbol.ToDisplayString(FullNameFormat);
+        if (typeSymbol.TypeParameters.Length > 0)
+        {
+            return new TypeModel(
+                typeName,
+                string.Empty,
+                DeclKind.Unknown,
+                ImmutableArray<MemberModel>.Empty,
+                ImmutableArray<MemberModel>.Empty,
+                ImmutableArray.Create(Diagnostic.Create(
+                    GenericTypeNotSupported,
+                    typeSymbol.Locations.FirstOrDefault(),
+                    typeName)));
+        }
+
         string serializerName = "__ForySerializer_" + 
Sanitize(typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat));
-        if (typeSymbol.TypeKind == TypeKind.Enum)
+        if (attributeKind == ForyAttributeKind.Enum)
         {
+            if (typeSymbol.TypeKind != TypeKind.Enum)
+            {
+                return null;
+            }
+
             return new TypeModel(
                 typeName,
                 serializerName,
@@ -2138,6 +2180,36 @@ public sealed class ForyObjectGenerator : 
IIncrementalGenerator
                 ImmutableArray<Diagnostic>.Empty);
         }
 
+        if (attributeKind == ForyAttributeKind.Union)
+        {
+            if (typeSymbol.TypeKind != TypeKind.Class)
+            {
+                return null;
+            }
+
+            if (!IsUnionType(typeSymbol))
+            {
+                return new TypeModel(
+                    typeName,
+                    serializerName,
+                    DeclKind.Union,
+                    ImmutableArray<MemberModel>.Empty,
+                    ImmutableArray<MemberModel>.Empty,
+                    ImmutableArray.Create(Diagnostic.Create(
+                        InvalidUnionType,
+                        typeSymbol.Locations.FirstOrDefault(),
+                        typeName)));
+            }
+
+            return new TypeModel(
+                typeName,
+                serializerName,
+                DeclKind.Union,
+                ImmutableArray<MemberModel>.Empty,
+                ImmutableArray<MemberModel>.Empty,
+                ImmutableArray<Diagnostic>.Empty);
+        }
+
         DeclKind kind = typeSymbol.TypeKind switch
         {
             TypeKind.Struct => DeclKind.Struct,
@@ -2152,7 +2224,16 @@ public sealed class ForyObjectGenerator : 
IIncrementalGenerator
 
         if (kind == DeclKind.Class && 
!HasAccessibleParameterlessCtor(typeSymbol))
         {
-            return null;
+            return new TypeModel(
+                typeName,
+                serializerName,
+                kind,
+                ImmutableArray<MemberModel>.Empty,
+                ImmutableArray<MemberModel>.Empty,
+                ImmutableArray.Create(Diagnostic.Create(
+                    MissingCtor,
+                    typeSymbol.Locations.FirstOrDefault(),
+                    typeName)));
         }
 
         List<Diagnostic> diagnostics = [];
@@ -2219,6 +2300,30 @@ public sealed class ForyObjectGenerator : 
IIncrementalGenerator
         return new TypeModel(typeName, serializerName, kind, ordered, sorted, 
diagnostics.ToImmutableArray());
     }
 
+    private static ForyAttributeKind GetForyAttributeKind(INamedTypeSymbol 
typeSymbol)
+    {
+        foreach (AttributeData attribute in typeSymbol.GetAttributes())
+        {
+            string? attrName = attribute.AttributeClass?.ToDisplayString();
+            if (string.Equals(attrName, "Apache.Fory.ForyStructAttribute", 
StringComparison.Ordinal))
+            {
+                return ForyAttributeKind.Struct;
+            }
+
+            if (string.Equals(attrName, "Apache.Fory.ForyEnumAttribute", 
StringComparison.Ordinal))
+            {
+                return ForyAttributeKind.Enum;
+            }
+
+            if (string.Equals(attrName, "Apache.Fory.ForyUnionAttribute", 
StringComparison.Ordinal))
+            {
+                return ForyAttributeKind.Union;
+            }
+        }
+
+        return ForyAttributeKind.None;
+    }
+
     private static MemberModel? BuildMemberModel(
         string name,
         ITypeSymbol memberType,
@@ -3624,6 +3729,15 @@ public sealed class ForyObjectGenerator : 
IIncrementalGenerator
         Class,
         Struct,
         Enum,
+        Union,
+    }
+
+    private enum ForyAttributeKind
+    {
+        None,
+        Struct,
+        Enum,
+        Union,
     }
 
     private enum DynamicAnyKind
diff --git a/csharp/src/Fory/Attributes.cs b/csharp/src/Fory/Attributes.cs
index e18be220d..ec3553c28 100644
--- a/csharp/src/Fory/Attributes.cs
+++ b/csharp/src/Fory/Attributes.cs
@@ -18,10 +18,10 @@
 namespace Apache.Fory;
 
 /// <summary>
-/// Marks a class, struct, or enum as a generated Fory object type.
+/// Marks a class or struct as a generated Fory struct type.
 /// </summary>
-[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | 
AttributeTargets.Enum)]
-public sealed class ForyObjectAttribute : Attribute
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
+public sealed class ForyStructAttribute : Attribute
 {
     /// <summary>
     /// Whether the annotated struct should use schema evolution metadata in 
compatible mode.
@@ -29,6 +29,22 @@ public sealed class ForyObjectAttribute : Attribute
     public bool Evolving { get; set; } = true;
 }
 
+/// <summary>
+/// Marks an enum as a generated Fory enum type.
+/// </summary>
+[AttributeUsage(AttributeTargets.Enum)]
+public sealed class ForyEnumAttribute : Attribute
+{
+}
+
+/// <summary>
+/// Marks a <see cref="Union"/> subclass as a generated Fory union type.
+/// </summary>
+[AttributeUsage(AttributeTargets.Class)]
+public sealed class ForyUnionAttribute : Attribute
+{
+}
+
 /// <summary>
 /// Overrides generated serializer behavior for a field or property.
 /// </summary>
diff --git a/csharp/src/Fory/TypeInfo.cs b/csharp/src/Fory/TypeInfo.cs
index 95c47ec86..65a040a21 100644
--- a/csharp/src/Fory/TypeInfo.cs
+++ b/csharp/src/Fory/TypeInfo.cs
@@ -169,7 +169,7 @@ public sealed class TypeInfo
         }
 
         Type structType = Nullable.GetUnderlyingType(type) ?? type;
-        ForyObjectAttribute? attribute = 
structType.GetCustomAttribute<ForyObjectAttribute>();
+        ForyStructAttribute? attribute = 
structType.GetCustomAttribute<ForyStructAttribute>();
         return attribute?.Evolving ?? true;
     }
 
diff --git a/csharp/tests/Fory.Tests/ForyGeneratorTests.cs 
b/csharp/tests/Fory.Tests/ForyGeneratorTests.cs
index 5c2a32a22..efca15158 100644
--- a/csharp/tests/Fory.Tests/ForyGeneratorTests.cs
+++ b/csharp/tests/Fory.Tests/ForyGeneratorTests.cs
@@ -24,6 +24,57 @@ namespace Apache.Fory.Tests;
 
 public sealed class ForyGeneratorTests
 {
+    [Fact]
+    public void SplitAttributesCompile()
+    {
+        const string source = """
+            using Apache.Fory;
+
+            namespace GeneratedDiagnostics;
+
+            [ForyEnum]
+            public enum Status
+            {
+                Ready,
+                Done,
+            }
+
+            [ForyUnion]
+            public sealed class Choice : Union
+            {
+                private Choice(int index, object? value)
+                    : base(index, value)
+                {
+                }
+
+                public static Choice Of(int index, object? value)
+                {
+                    return new Choice(index, value);
+                }
+
+                public static Choice Text(string value)
+                {
+                    return new Choice(1, value);
+                }
+            }
+
+            [ForyStruct]
+            public sealed class Envelope
+            {
+                public Status Status { get; set; }
+                public Choice Choice { get; set; } = Choice.Text(string.Empty);
+            }
+            """;
+
+        CSharpCompilation compilation = CreateCompilation(source);
+        GeneratorDriver driver = CSharpGeneratorDriver.Create(new 
ForyModelGenerator());
+        driver.RunGeneratorsAndUpdateCompilation(compilation, out Compilation 
output, out ImmutableArray<Diagnostic> diagnostics);
+
+        Assert.DoesNotContain(
+            diagnostics.Concat(output.GetDiagnostics()),
+            diagnostic => diagnostic.Severity == DiagnosticSeverity.Error);
+    }
+
     [Fact]
     public void NegativeForyFieldIdReportsDiagnostic()
     {
@@ -32,7 +83,7 @@ public sealed class ForyGeneratorTests
 
             namespace GeneratedDiagnostics;
 
-            [ForyObject]
+            [ForyStruct]
             public sealed class InvalidFieldId
             {
                 [ForyField(-1)]
@@ -41,7 +92,7 @@ public sealed class ForyGeneratorTests
             """;
 
         CSharpCompilation compilation = CreateCompilation(source);
-        GeneratorDriver driver = CSharpGeneratorDriver.Create(new 
ForyObjectGenerator());
+        GeneratorDriver driver = CSharpGeneratorDriver.Create(new 
ForyModelGenerator());
         driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out 
Compilation output, out ImmutableArray<Diagnostic> diagnostics);
 
         ImmutableArray<Diagnostic> generatorDiagnostics = 
driver.GetRunResult().Diagnostics;
@@ -56,7 +107,7 @@ public sealed class ForyGeneratorTests
             .Split(Path.PathSeparator)
             .Select(path => MetadataReference.CreateFromFile(path));
         MetadataReference foryReference =
-            
MetadataReference.CreateFromFile(typeof(ForyObjectAttribute).Assembly.Location);
+            
MetadataReference.CreateFromFile(typeof(ForyStructAttribute).Assembly.Location);
 
         return CSharpCompilation.Create(
             "ForyGeneratorDiagnostics",
diff --git a/csharp/tests/Fory.Tests/ForyRuntimeTests.cs 
b/csharp/tests/Fory.Tests/ForyRuntimeTests.cs
index 0b312164b..0fcee13b3 100644
--- a/csharp/tests/Fory.Tests/ForyRuntimeTests.cs
+++ b/csharp/tests/Fory.Tests/ForyRuntimeTests.cs
@@ -26,7 +26,7 @@ using S = Apache.Fory.Schema.Types;
 
 namespace Apache.Fory.Tests;
 
-[ForyObject]
+[ForyEnum]
 public enum TestColor
 {
     Green,
@@ -35,14 +35,14 @@ public enum TestColor
     White,
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class Address
 {
     public string Street { get; set; } = string.Empty;
     public int Zip { get; set; }
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class Person
 {
     public long Id { get; set; }
@@ -54,14 +54,14 @@ public sealed class Person
     public Dictionary<sbyte, int?> Metadata { get; set; } = [];
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class Node
 {
     public int Value { get; set; }
     public Node? Next { get; set; }
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class FieldOrder
 {
     public string Z { get; set; } = string.Empty;
@@ -70,7 +70,7 @@ public sealed class FieldOrder
     public int C { get; set; }
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class NonPrimitiveFieldOrder
 {
     [ForyField(20)]
@@ -84,7 +84,7 @@ public sealed class NonPrimitiveFieldOrder
     public int IntValue { get; set; }
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class SchemaNumbers
 {
     [ForyField(Type = typeof(S.Fixed<S.UInt32>))]
@@ -94,21 +94,21 @@ public sealed class SchemaNumbers
     public ulong U64Tagged { get; set; }
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class NestedSchemaByName
 {
     [ForyField(Type = typeof(S.Map<S.Fixed<S.UInt32>, 
S.List<S.Tagged<S.UInt64>>>))]
     public Dictionary<uint, List<ulong?>?> Values { get; set; } = [];
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class NestedSchemaById
 {
     [ForyField(3, Type = typeof(S.Map<S.Fixed<S.UInt32>, 
S.List<S.Tagged<S.UInt64>>>))]
     public Dictionary<uint, List<ulong?>?> Values { get; set; } = [];
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class NestedSchemaSkipWriter
 {
     [ForyField(Type = typeof(S.Map<S.Fixed<S.UInt32>, 
S.List<S.Tagged<S.UInt64>>>))]
@@ -117,76 +117,76 @@ public sealed class NestedSchemaSkipWriter
     public int Tail { get; set; }
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class DefaultListSchema
 {
     public List<int> Values { get; set; } = [];
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class ExplicitArraySchema
 {
     [ForyField(Type = typeof(S.Array<S.Int32>))]
     public int[] Values { get; set; } = [];
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class CompatibleListSchema
 {
     [ForyField(Type = typeof(S.List<S.Int32>))]
     public List<int> Values { get; set; } = [];
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class CompatibleNullableListSchema
 {
     [ForyField(Type = typeof(S.List<S.Int32>))]
     public List<int?> Values { get; set; } = [];
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class CompatibleArraySchema
 {
     [ForyField(Type = typeof(S.Array<S.Int32>))]
     public int[] Values { get; set; } = [];
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class CompatibleUInt32ListSchema
 {
     [ForyField(Type = typeof(S.List<S.UInt32>))]
     public List<uint> Values { get; set; } = [];
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class CompatibleUInt32ArraySchema
 {
     [ForyField(Type = typeof(S.Array<S.UInt32>))]
     public uint[] Values { get; set; } = [];
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class CompatibleUInt32ArrayListCarrierSchema
 {
     [ForyField(Type = typeof(S.Array<S.UInt32>))]
     public List<uint> Values { get; set; } = [];
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class CompatibleNestedListSchema
 {
     [ForyField(Type = typeof(S.Map<S.String, S.List<S.Int32>>))]
     public Dictionary<string, List<int>> Values { get; set; } = [];
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class CompatibleNestedArraySchema
 {
     [ForyField(Type = typeof(S.Map<S.String, S.Array<S.Int32>>))]
     public Dictionary<string, int[]> Values { get; set; } = [];
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class SemanticScalarSchema
 {
     [ForyField(Type = typeof(S.Int32))]
@@ -199,62 +199,62 @@ public sealed class SemanticScalarSchema
     public long TaggedI64 { get; set; }
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class NestedSchemaSkipReader
 {
     public int Tail { get; set; }
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class OneStringField
 {
     public string? F1 { get; set; }
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class TwoStringField
 {
     public string F1 { get; set; } = string.Empty;
     public string F2 { get; set; } = string.Empty;
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class EvolvingOverrideValue
 {
     public string F1 { get; set; } = string.Empty;
 }
 
-[ForyObject(Evolving = false)]
+[ForyStruct(Evolving = false)]
 public sealed class FixedOverrideValue
 {
     public string F1 { get; set; } = string.Empty;
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class OneStringFieldListHolder
 {
     public List<OneStringField?> Items { get; set; } = [];
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class TwoStringFieldListHolder
 {
     public List<TwoStringField?> Items { get; set; } = [];
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class OneStringFieldMapHolder
 {
     public Dictionary<string, OneStringField?> Items { get; set; } = [];
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class TwoStringFieldMapHolder
 {
     public Dictionary<string, TwoStringField?> Items { get; set; } = [];
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class UnsignedFields
 {
     public byte U8 { get; set; }
@@ -267,7 +267,7 @@ public sealed class UnsignedFields
     public ulong? U64Nullable { get; set; }
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class StructWithEnum
 {
     public string Name { get; set; } = string.Empty;
@@ -275,19 +275,19 @@ public sealed class StructWithEnum
     public int Value { get; set; }
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class StructWithNullableMap
 {
     public NullableKeyDictionary<string, string?> Data { get; set; } = new();
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class StructWithUnion2
 {
     public Union2<string, long> Union { get; set; } = Union2<string, 
long>.OfT1(string.Empty);
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class DynamicAnyHolder
 {
     public object? AnyValue { get; set; }
@@ -295,7 +295,7 @@ public sealed class DynamicAnyHolder
     public Dictionary<object, object?> AnyMap { get; set; } = [];
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class DictionaryContainerHolder
 {
     public Dictionary<string, int> DictionaryField { get; set; } = [];
@@ -304,7 +304,7 @@ public sealed class DictionaryContainerHolder
     public ConcurrentDictionary<string, int> ConcurrentField { get; set; } = 
new();
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class CollectionContainerHolder
 {
     public LinkedList<int> LinkedListField { get; set; } = new();
@@ -314,7 +314,7 @@ public sealed class CollectionContainerHolder
     public Stack<int> StackField { get; set; } = new();
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class NestedTypedContainers
 {
     public List<List<string>> NestedLists { get; set; } = [];
diff --git a/csharp/tests/Fory.Tests/RuntimeEdgeCaseTests.cs 
b/csharp/tests/Fory.Tests/RuntimeEdgeCaseTests.cs
index 3b3011a3f..8e8f38ba1 100644
--- a/csharp/tests/Fory.Tests/RuntimeEdgeCaseTests.cs
+++ b/csharp/tests/Fory.Tests/RuntimeEdgeCaseTests.cs
@@ -21,7 +21,7 @@ using ForyRuntime = Apache.Fory.Fory;
 
 namespace Apache.Fory.Tests;
 
-[ForyObject]
+[ForyStruct]
 public sealed class TimeEnvelope
 {
     public DateOnly Date { get; set; }
@@ -34,7 +34,7 @@ public sealed class TimeEnvelope
     public List<TimeSpan> Durations { get; set; } = [];
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class NullableEnvelope
 {
     public int? Int32Value { get; set; }
@@ -43,14 +43,14 @@ public sealed class NullableEnvelope
     public TestColor? Color { get; set; }
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class CustomPayload
 {
     public int Id { get; set; }
     public string Marker { get; set; } = string.Empty;
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class DecimalEnvelope
 {
     public ForyDecimal Exact { get; set; }
diff --git a/csharp/tests/Fory.XlangPeer/Program.cs 
b/csharp/tests/Fory.XlangPeer/Program.cs
index 2fad7dfbf..fad0af740 100644
--- a/csharp/tests/Fory.XlangPeer/Program.cs
+++ b/csharp/tests/Fory.XlangPeer/Program.cs
@@ -1135,7 +1135,7 @@ internal static class Program
     }
 }
 
-[ForyObject]
+[ForyEnum]
 public enum Color
 {
     Green,
@@ -1144,13 +1144,13 @@ public enum Color
     White,
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class Item
 {
     public string Name { get; set; } = string.Empty;
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class SimpleStruct
 {
     public Dictionary<int, double> F1 { get; set; } = [];
@@ -1164,19 +1164,19 @@ public sealed class SimpleStruct
     public int Last { get; set; }
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class EvolvingOverrideStruct
 {
     public string F1 { get; set; } = string.Empty;
 }
 
-[ForyObject(Evolving = false)]
+[ForyStruct(Evolving = false)]
 public sealed class FixedOverrideStruct
 {
     public string F1 { get; set; } = string.Empty;
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class Item1
 {
     public int F1 { get; set; }
@@ -1187,31 +1187,31 @@ public sealed class Item1
     public int F6 { get; set; }
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class StructWithList
 {
     public List<string?> Items { get; set; } = [];
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class StructWithMap
 {
     public NullableKeyDictionary<string, string?> Data { get; set; } = new();
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class StructWithUnion2
 {
     public Union2<string, long> Union { get; set; } = Union2<string, 
long>.OfT1(string.Empty);
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class MyStruct
 {
     public int Id { get; set; }
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class MyExt
 {
     public int Id { get; set; }
@@ -1233,7 +1233,7 @@ public sealed class MyExtSerializer : Serializer<MyExt>
     }
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class MyWrapper
 {
     public Color Color { get; set; }
@@ -1241,12 +1241,12 @@ public sealed class MyWrapper
     public MyStruct MyStruct { get; set; } = new();
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class EmptyWrapper
 {
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class VersionCheckStruct
 {
     public int F1 { get; set; }
@@ -1254,57 +1254,57 @@ public sealed class VersionCheckStruct
     public double F3 { get; set; }
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class Dog
 {
     public int Age { get; set; }
     public string? Name { get; set; }
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class Cat
 {
     public int Age { get; set; }
     public int Lives { get; set; }
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class AnimalListHolder
 {
     public List<object?> Animals { get; set; } = [];
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class AnimalMapHolder
 {
     public Dictionary<string, object?> AnimalMap { get; set; } = [];
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class OneFieldStruct
 {
     public int Value { get; set; }
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class OneStringFieldStruct
 {
     public string? F1 { get; set; }
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class TwoStringFieldStruct
 {
     public string F1 { get; set; } = string.Empty;
     public string F2 { get; set; } = string.Empty;
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class EmptyStruct
 {
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class ReducedPrecisionFloatStruct
 {
     public Half Float16Value { get; set; }
@@ -1313,28 +1313,28 @@ public sealed class ReducedPrecisionFloatStruct
     public List<BFloat16> BFloat16Array { get; set; } = [];
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class CompatibleInt32ListField
 {
     [ForyField(1, Type = typeof(S.List<S.Fixed<S.Int32>>))]
     public List<int> Values { get; set; } = [];
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class CompatibleNullableInt32ListField
 {
     [ForyField(1, Type = typeof(S.List<S.Fixed<S.Int32>>))]
     public List<int?> Values { get; set; } = [];
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class CompatibleInt32ArrayField
 {
     [ForyField(1, Type = typeof(S.Array<S.Int32>))]
     public int[] Values { get; set; } = [];
 }
 
-[ForyObject]
+[ForyEnum]
 public enum TestEnum
 {
     ValueA,
@@ -1342,20 +1342,20 @@ public enum TestEnum
     ValueC,
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class OneEnumFieldStruct
 {
     public TestEnum F1 { get; set; }
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class TwoEnumFieldStruct
 {
     public TestEnum F1 { get; set; }
     public TestEnum F2 { get; set; }
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class NullableComprehensiveSchemaConsistent
 {
     public sbyte ByteField { get; set; }
@@ -1382,7 +1382,7 @@ public sealed class NullableComprehensiveSchemaConsistent
     public NullableKeyDictionary<string, string>? NullableMap { get; set; }
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class NullableComprehensiveCompatible
 {
     public sbyte ByteField { get; set; }
@@ -1416,42 +1416,42 @@ public sealed class NullableComprehensiveCompatible
     public NullableKeyDictionary<string, string> NullableMap2 { get; set; } = 
new();
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class RefInnerSchemaConsistent
 {
     public int Id { get; set; }
     public string Name { get; set; } = string.Empty;
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class RefOuterSchemaConsistent
 {
     public RefInnerSchemaConsistent? Inner1 { get; set; }
     public RefInnerSchemaConsistent? Inner2 { get; set; }
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class RefInnerCompatible
 {
     public int Id { get; set; }
     public string Name { get; set; } = string.Empty;
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class RefOuterCompatible
 {
     public RefInnerCompatible? Inner1 { get; set; }
     public RefInnerCompatible? Inner2 { get; set; }
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class RefOverrideElement
 {
     public int Id { get; set; }
     public string Name { get; set; } = string.Empty;
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class RefOverrideContainer
 {
     public List<RefOverrideElement?> ListField { get; set; } = [];
@@ -1459,14 +1459,14 @@ public sealed class RefOverrideContainer
     public Dictionary<string, RefOverrideElement?> MapField { get; set; } = [];
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class CircularRefStruct
 {
     public string Name { get; set; } = string.Empty;
     public CircularRefStruct? SelfRef { get; set; }
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class UnsignedSchemaConsistentSimple
 {
     [ForyField(Type = typeof(S.Tagged<S.UInt64>))]
@@ -1476,7 +1476,7 @@ public sealed class UnsignedSchemaConsistentSimple
     public ulong? U64TaggedNullable { get; set; }
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class UnsignedSchemaConsistent
 {
     public byte U8Field { get; set; }
@@ -1510,7 +1510,7 @@ public sealed class UnsignedSchemaConsistent
     public ulong? U64TaggedNullableField { get; set; }
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class UnsignedSchemaCompatible
 {
     public byte? U8Field1 { get; set; }
@@ -1545,14 +1545,14 @@ public sealed class UnsignedSchemaCompatible
 }
 
 #pragma warning disable CS8714
-[ForyObject]
+[ForyStruct]
 public sealed class NestedAnnotatedContainerSchemaConsistent
 {
     [ForyField(Type = typeof(S.Map<S.Fixed<S.UInt32>, 
S.List<S.Tagged<S.UInt64>>>))]
     public NullableKeyDictionary<uint?, List<ulong?>?> Values { get; set; } = 
new();
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class NestedAnnotatedContainerCompatible
 {
     [ForyField(Type = typeof(S.Map<S.Fixed<S.UInt32>, 
S.List<S.Tagged<S.UInt64>>>))]
diff --git a/docs/compiler/generated-code.md b/docs/compiler/generated-code.md
index ed2d02c05..3c9d8899f 100644
--- a/docs/compiler/generated-code.md
+++ b/docs/compiler/generated-code.md
@@ -771,10 +771,10 @@ C# output is one `.cs` file per schema, for example:
 
 ### Type Generation
 
-Messages generate `[ForyObject]` classes with C# properties and byte helpers:
+Messages generate `[ForyStruct]` classes with C# properties and byte helpers:
 
 ```csharp
-[ForyObject]
+[ForyStruct]
 public sealed partial class Person
 {
     public string Name { get; set; } = string.Empty;
@@ -787,9 +787,10 @@ public sealed partial class Person
 }
 ```
 
-Unions generate `Union` subclasses with typed case helpers:
+Unions generate `[ForyUnion]` `Union` subclasses with typed case helpers:
 
 ```csharp
+[ForyUnion]
 public sealed class Animal : Union
 {
     public static Animal Dog(Dog value) { ... }
diff --git a/docs/compiler/index.md b/docs/compiler/index.md
index b61f49917..5d97bb2d9 100644
--- a/docs/compiler/index.md
+++ b/docs/compiler/index.md
@@ -126,9 +126,9 @@ Generated code uses native language constructs:
 - Java: Plain POJOs with `@ForyField` annotations
 - Python: Dataclasses with type hints
 - Go: Structs with struct tags
-- Rust: Structs with `#[derive(ForyObject)]`
+- Rust: Structs with `#[derive(ForyStruct)]`
 - C++: Structs with `FORY_STRUCT` macros
-- C#: Classes with `[ForyObject]` and registration helpers
+- C#: `[ForyStruct]` classes, `[ForyEnum]` enums, `[ForyUnion]` unions, and 
registration helpers
 - JavaScript/TypeScript: Interfaces with registration function
 - Swift: Fory model macros with field/case metadata and registration helpers
 - Dart: `@ForyStruct` classes with `@ForyField` annotations and registration 
helpers
diff --git a/docs/compiler/schema-idl.md b/docs/compiler/schema-idl.md
index f5cd9da4c..e7f59e203 100644
--- a/docs/compiler/schema-idl.md
+++ b/docs/compiler/schema-idl.md
@@ -714,9 +714,9 @@ message Person {  // Auto-generated when 
enable_auto_type_id = true
 | Java                  | POJO class with getters/setters     |
 | Python                | `@dataclass` class                  |
 | Go                    | Struct with exported fields         |
-| Rust                  | Struct with `#[derive(ForyObject)]` |
+| Rust                  | Struct with `#[derive(ForyStruct)]` |
 | C++                   | Struct with `FORY_STRUCT` macro     |
-| C#                    | `[ForyObject]` class                |
+| C#                    | `[ForyStruct]` class                |
 | JavaScript/TypeScript | `export interface` declaration      |
 | Swift                 | `@ForyStruct` struct or class       |
 | Dart                  | `@ForyStruct` `final class`         |
diff --git a/docs/guide/csharp/basic-serialization.md 
b/docs/guide/csharp/basic-serialization.md
index a7a7edffb..d112990cc 100644
--- a/docs/guide/csharp/basic-serialization.md
+++ b/docs/guide/csharp/basic-serialization.md
@@ -23,19 +23,19 @@ This page covers typed serialization APIs in Apache Fory™ 
C#.
 
 ## Object Graph Serialization
 
-Use `[ForyObject]` on your classes/structs and register them before use.
+Use `[ForyStruct]` on your classes/structs and register them before use.
 
 ```csharp
 using Apache.Fory;
 
-[ForyObject]
+[ForyStruct]
 public sealed class Address
 {
     public string Street { get; set; } = string.Empty;
     public int Zip { get; set; }
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class Person
 {
     public long Id { get; set; }
@@ -122,7 +122,7 @@ fory.Serialize<object?>(dynamicWriter, value);
 
 - Reuse the same `Fory` or `ThreadSafeFory` instance for better performance.
 - Primitive types and collections do not require user registration.
-- User `[ForyObject]` and custom serializer types should be registered 
explicitly.
+- User `[ForyStruct]`, `[ForyEnum]`, `[ForyUnion]`, and custom serializer 
types should be registered explicitly.
 
 ## Related Topics
 
diff --git a/docs/guide/csharp/custom-serializers.md 
b/docs/guide/csharp/custom-serializers.md
index 164a32a48..9e7ef768f 100644
--- a/docs/guide/csharp/custom-serializers.md
+++ b/docs/guide/csharp/custom-serializers.md
@@ -19,7 +19,7 @@ license: |
   limitations under the License.
 ---
 
-Use custom serializers when a type is not generated with `[ForyObject]` or 
requires specialized encoding.
+Use custom serializers when a type is not generated with `[ForyStruct]` or 
requires specialized encoding.
 
 ## Implement `Serializer<T>`
 
@@ -75,7 +75,7 @@ Point decoded = fory.Deserialize<Point>(payload);
 1. Keep serializers deterministic and symmetric.
 2. Use varint/fixed/tagged encoding intentionally for integer-heavy payloads.
 3. Register custom serializers on all reader/writer peers.
-4. Prefer generated `[ForyObject]` serializers for normal domain models.
+4. Prefer generated `[ForyStruct]` serializers for normal domain models.
 
 ## Related Topics
 
diff --git a/docs/guide/csharp/index.md b/docs/guide/csharp/index.md
index 9fbb06b01..1b556091b 100644
--- a/docs/guide/csharp/index.md
+++ b/docs/guide/csharp/index.md
@@ -26,7 +26,7 @@ Apache Fory™ C# is a high-performance, cross-language 
serialization runtime fo
 - High performance binary serialization for .NET 8+
 - Xlang compatibility with Fory implementations in Java, Python, C++, Go, Rust,
   JavaScript/TypeScript, Swift, Dart, Scala, and Kotlin
-- Source-generator-based serializers for `[ForyObject]` types
+- Source-generator-based serializers for `[ForyStruct]` types, plus 
`[ForyEnum]` and `[ForyUnion]` registration
 - Optional reference tracking for shared and circular object graphs
 - Compatible mode for schema evolution
 - Thread-safe runtime (`ThreadSafeFory`) for multi-threaded services
@@ -40,7 +40,7 @@ Apache Fory™ C# is a high-performance, cross-language 
serialization runtime fo
 
 ### Install from NuGet
 
-Reference the single `Apache.Fory` package. It includes the runtime and the 
source generator for `[ForyObject]` types.
+Reference the single `Apache.Fory` package. It includes the runtime and the 
source generator for `[ForyStruct]`, `[ForyEnum]`, and `[ForyUnion]` types.
 
 ```xml
 <ItemGroup>
@@ -53,7 +53,7 @@ Reference the single `Apache.Fory` package. It includes the 
runtime and the sour
 ```csharp
 using Apache.Fory;
 
-[ForyObject]
+[ForyStruct]
 public sealed class User
 {
     public long Id { get; set; }
diff --git a/docs/guide/csharp/references.md b/docs/guide/csharp/references.md
index 1420be04f..772b2535c 100644
--- a/docs/guide/csharp/references.md
+++ b/docs/guide/csharp/references.md
@@ -39,7 +39,7 @@ When enabled:
 ```csharp
 using Apache.Fory;
 
-[ForyObject]
+[ForyStruct]
 public sealed class Node
 {
     public int Value { get; set; }
diff --git a/docs/guide/csharp/schema-evolution.md 
b/docs/guide/csharp/schema-evolution.md
index 10efda027..108634b77 100644
--- a/docs/guide/csharp/schema-evolution.md
+++ b/docs/guide/csharp/schema-evolution.md
@@ -36,13 +36,13 @@ Compatible mode writes type metadata that allows readers 
and writers with differ
 ```csharp
 using Apache.Fory;
 
-[ForyObject]
+[ForyStruct]
 public sealed class OneStringField
 {
     public string? F1 { get; set; }
 }
 
-[ForyObject]
+[ForyStruct]
 public sealed class TwoStringField
 {
     public string F1 { get; set; } = string.Empty;
diff --git a/docs/guide/csharp/schema-metadata.md 
b/docs/guide/csharp/schema-metadata.md
index 3941a3824..37ebd33a7 100644
--- a/docs/guide/csharp/schema-metadata.md
+++ b/docs/guide/csharp/schema-metadata.md
@@ -21,15 +21,15 @@ license: |
 
 This page covers field-level serializer configuration for C# generated 
serializers.
 
-## `[ForyObject]` and `[ForyField]`
+## `[ForyStruct]` and `[ForyField]`
 
-Use `[ForyObject]` to enable source-generated serializers. Use `[ForyField]` 
to assign an optional stable non-negative field id or to override the Fory 
schema type used for a field.
+Use `[ForyStruct]` to enable source-generated serializers. Use `[ForyField]` 
to assign an optional stable non-negative field id or to override the Fory 
schema type used for a field.
 
 ```csharp
 using Apache.Fory;
 using S = Apache.Fory.Schema.Types;
 
-[ForyObject]
+[ForyStruct]
 public sealed class Metrics
 {
     [ForyField(Type = typeof(S.UInt32))]
@@ -48,7 +48,7 @@ public sealed class Metrics
 using Apache.Fory;
 using S = Apache.Fory.Schema.Types;
 
-[ForyObject]
+[ForyStruct]
 public sealed class NestedMetrics
 {
     [ForyField(Type = typeof(S.Map<S.Fixed<S.UInt32>, 
S.List<S.Tagged<S.UInt64>>>))]
diff --git a/docs/guide/csharp/supported-types.md 
b/docs/guide/csharp/supported-types.md
index fed11cd45..e93b59221 100644
--- a/docs/guide/csharp/supported-types.md
+++ b/docs/guide/csharp/supported-types.md
@@ -76,7 +76,7 @@ This page summarizes built-in and generated type support in 
Apache Fory™ C#.
 
 ## User Types
 
-- `[ForyObject]` classes/structs/enums via source-generated serializers
+- `[ForyStruct]` classes/structs via source-generated serializers, plus 
`[ForyEnum]` enums and `[ForyUnion]` union subclasses
 - Custom serializer types registered through `Register<T, TSerializer>(...)`
 - `Union` / `Union2<...>` typed union support
 
diff --git a/docs/guide/csharp/xlang-serialization.md 
b/docs/guide/csharp/xlang-serialization.md
index 3c53e52af..d47b44ded 100644
--- a/docs/guide/csharp/xlang-serialization.md
+++ b/docs/guide/csharp/xlang-serialization.md
@@ -36,7 +36,7 @@ Fory fory = Fory.Builder()
 ## Register with Stable IDs
 
 ```csharp
-[ForyObject]
+[ForyStruct]
 public sealed class Person
 {
     public string Name { get; set; } = string.Empty;
diff --git a/docs/guide/dart/xlang-serialization.md 
b/docs/guide/dart/xlang-serialization.md
index 38ec6dc09..b8d5d2811 100644
--- a/docs/guide/dart/xlang-serialization.md
+++ b/docs/guide/dart/xlang-serialization.md
@@ -112,7 +112,7 @@ final bytes = fory.serialize(Person()
 ### CSharp
 
 ```csharp
-[ForyObject]
+[ForyStruct]
 public sealed class Person
 {
     public string Name { get; set; } = string.Empty;
diff --git a/docs/guide/go/type-registration.md 
b/docs/guide/go/type-registration.md
index 452cdb5be..d651fb1a9 100644
--- a/docs/guide/go/type-registration.md
+++ b/docs/guide/go/type-registration.md
@@ -214,9 +214,9 @@ fory.register(User, typename="example.User")
 **Rust**:
 
 ```rust
-use fory::{Fory, ForyObject};
+use fory::{Fory, ForyStruct};
 
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
 struct User {
     id: i64,
     name: String,
diff --git a/docs/guide/python/xlang-serialization.md 
b/docs/guide/python/xlang-serialization.md
index cf1bd19b5..a16da3b7b 100644
--- a/docs/guide/python/xlang-serialization.md
+++ b/docs/guide/python/xlang-serialization.md
@@ -79,9 +79,9 @@ Person person = (Person) fory.deserialize(binaryData);
 
 ```rust
 use fory::Fory;
-use fory::ForyObject;
+use fory::ForyStruct;
 
-#[derive(ForyObject)]
+#[derive(ForyStruct)]
 struct Person {
     name: String,
     age: i32,
diff --git a/docs/specification/xlang_type_mapping.md 
b/docs/specification/xlang_type_mapping.md
index 6769b1ca9..9894bbb1f 100644
--- a/docs/specification/xlang_type_mapping.md
+++ b/docs/specification/xlang_type_mapping.md
@@ -80,15 +80,15 @@ FDL spells them as an encoding modifier plus a semantic 
integer type.
 | list                               | 22           | List/Collection          
                 | list/tuple                                | array            
                     | vector                                              | 
slice                                          | Vec                            
   | `List<T>`                          | `[T]`                    | `List<T>`  
                 | `List[T]`                       | `List<T>`              |
 | set                                | 23           | Set                      
                 | set                                       | /                
                     | set                                                 | 
fory.Set                                       | Set                            
   | `HashSet<T>`                       | `Set<T>`                 | `Set<T>`   
                 | `Set[T]`                        | `Set<T>`               |
 | map                                | 24           | Map                      
                 | dict                                      | Map              
                     | unordered_map                                       | 
map                                            | HashMap                        
   | `Dictionary<K,V>`                  | `[K: V]`                 | `Map<K, 
V>`                 | `Map[K, V]`                     | `Map<K, V>`            |
-| enum                               | 25           | Enum subclasses          
                 | enum subclasses                           | /                
                     | enum                                                | /  
                                            | enum                              
| enum                               | enum                     | enum          
              | Scala 3 enum                    | enum class             |
-| named_enum                         | 26           | Enum subclasses          
                 | enum subclasses                           | /                
                     | enum                                                | /  
                                            | enum                              
| enum                               | enum                     | enum          
              | Scala 3 enum                    | enum class             |
-| struct                             | 27           | pojo/record              
                 | data class                                | object           
                     | struct/class                                        | 
struct                                         | struct                         
   | [ForyObject] class/struct          | @ForyStruct struct/class | 
@ForyStruct class           | case class/class                | data 
class/class       |
-| compatible_struct                  | 28           | pojo/record              
                 | data class                                | object           
                     | struct/class                                        | 
struct                                         | struct                         
   | [ForyObject] class/struct          | @ForyStruct struct/class | 
@ForyStruct class           | case class/class                | data 
class/class       |
-| named_struct                       | 29           | pojo/record              
                 | data class                                | object           
                     | struct/class                                        | 
struct                                         | struct                         
   | [ForyObject] class/struct          | @ForyStruct struct/class | 
@ForyStruct class           | case class/class                | data 
class/class       |
-| named_compatible_struct            | 30           | pojo/record              
                 | data class                                | object           
                     | struct/class                                        | 
struct                                         | struct                         
   | [ForyObject] class/struct          | @ForyStruct struct/class | 
@ForyStruct class           | case class/class                | data 
class/class       |
-| ext                                | 31           | pojo/record              
                 | data class                                | object           
                     | struct/class                                        | 
struct                                         | struct                         
   | [ForyObject] class/struct          | @ForyStruct struct/class | 
@ForyStruct class           | case class/class                | data 
class/class       |
-| named_ext                          | 32           | pojo/record              
                 | data class                                | object           
                     | struct/class                                        | 
struct                                         | struct                         
   | [ForyObject] class/struct          | @ForyStruct struct/class | 
@ForyStruct class           | case class/class                | data 
class/class       |
-| union                              | 33           | Union                    
                 | typing.Union                              | /                
                     | `std::variant<Ts...>`                               | /  
                                            | tagged union enum                 
| Union subclass                     | tagged enum              | @ForyUnion 
class            | ADT enum                        | sealed class           |
+| enum                               | 25           | Enum subclasses          
                 | enum subclasses                           | /                
                     | enum                                                | /  
                                            | enum                              
| `[ForyEnum]` enum                  | enum                     | enum          
              | Scala 3 enum                    | enum class             |
+| named_enum                         | 26           | Enum subclasses          
                 | enum subclasses                           | /                
                     | enum                                                | /  
                                            | enum                              
| `[ForyEnum]` enum                  | enum                     | enum          
              | Scala 3 enum                    | enum class             |
+| struct                             | 27           | pojo/record              
                 | data class                                | object           
                     | struct/class                                        | 
struct                                         | struct                         
   | `[ForyStruct]` class/struct        | @ForyStruct struct/class | 
@ForyStruct class           | case class/class                | data 
class/class       |
+| compatible_struct                  | 28           | pojo/record              
                 | data class                                | object           
                     | struct/class                                        | 
struct                                         | struct                         
   | `[ForyStruct]` class/struct        | @ForyStruct struct/class | 
@ForyStruct class           | case class/class                | data 
class/class       |
+| named_struct                       | 29           | pojo/record              
                 | data class                                | object           
                     | struct/class                                        | 
struct                                         | struct                         
   | `[ForyStruct]` class/struct        | @ForyStruct struct/class | 
@ForyStruct class           | case class/class                | data 
class/class       |
+| named_compatible_struct            | 30           | pojo/record              
                 | data class                                | object           
                     | struct/class                                        | 
struct                                         | struct                         
   | `[ForyStruct]` class/struct        | @ForyStruct struct/class | 
@ForyStruct class           | case class/class                | data 
class/class       |
+| ext                                | 31           | pojo/record              
                 | data class                                | object           
                     | struct/class                                        | 
struct                                         | struct                         
   | `[ForyStruct]` class/struct        | @ForyStruct struct/class | 
@ForyStruct class           | case class/class                | data 
class/class       |
+| named_ext                          | 32           | pojo/record              
                 | data class                                | object           
                     | struct/class                                        | 
struct                                         | struct                         
   | `[ForyStruct]` class/struct        | @ForyStruct struct/class | 
@ForyStruct class           | case class/class                | data 
class/class       |
+| union                              | 33           | Union                    
                 | typing.Union                              | /                
                     | `std::variant<Ts...>`                               | /  
                                            | tagged union enum                 
| `[ForyUnion]` Union subclass       | tagged enum              | @ForyUnion 
class            | ADT enum                        | sealed class           |
 | none                               | 36           | null                     
                 | None                                      | null             
                     | `std::monostate`                                    | 
nil                                            | `()`                           
   | null                               | nil                      | null       
                 | null                            | null                   |
 | duration                           | 37           | Duration                 
                 | timedelta                                 | Number           
                     | duration                                            | 
Duration                                       | Duration                       
   | TimeSpan                           | Duration                 | Duration   
                 | java.time.Duration              | kotlin.time.Duration   |
 | timestamp                          | 38           | Instant                  
                 | datetime                                  | Number           
                     | std::chrono::nanoseconds                            | 
Time                                           | Timestamp                      
   | DateTime/DateTimeOffset            | Date                     | Timestamp  
                 | java.time.Instant               | java.time.Instant      |


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

Reply via email to