diff --git a/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs b/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs
index 407bd1d..0b5add8 100644
--- a/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs
+++ b/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs
@@ -248,6 +248,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
typeNameForTypeof,
kind,
p.Type.NullableAnnotation == NullableAnnotation.Annotated || IsNullableVT(p.Type),
+ p.Type.SpecialType == SpecialType.System_Object,
stringInternAttr, hasGenWriter, propTypeIsIId, writerClassName, propIdTypeName,
elemKind, elemHasGenWriter, elemIsIId, elemWriterClassName, elemIdTypeName, collKind, elemFullTypeName,
childTypeNameHash, childPropertyHashes,
@@ -536,6 +537,22 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
// when the property type has a generated writer. Falls back to WriteObjectGenerated otherwise.
if (p.HasGeneratedWriter)
EmitDirectObjectWrite(sb, p, a, i);
+ else if (p.IsObjectDeclaredType)
+ {
+ // System.Object property: runtime type unknown at compile time.
+ // Write ObjectWithTypeName prefix so deserializer can resolve the concrete type.
+ // Use value.GetType() for runtime type dispatch (not typeof(object)).
+ sb.AppendLine($"{i}if ({a} == null) context.WriteByte(BinaryTypeCode.PropertySkip);");
+ sb.AppendLine($"{i}else");
+ sb.AppendLine($"{i}{{");
+ sb.AppendLine($"{i} if (!context.UseMetadata)");
+ sb.AppendLine($"{i} {{");
+ sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectWithTypeName);");
+ sb.AppendLine($"{i} context.WriteStringUtf8({a}.GetType().AssemblyQualifiedName!);");
+ sb.AppendLine($"{i} }}");
+ sb.AppendLine($"{i} AcBinarySerializer.WriteValueGenerated({a}, {a}.GetType(), context, depth);");
+ sb.AppendLine($"{i}}}");
+ }
else if (p.IsNullable)
{
sb.AppendLine($"{i}if ({a} == null) context.WriteByte(BinaryTypeCode.PropertySkip);");
@@ -723,13 +740,17 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
///
/// Emits scan pass code for a Complex property without SGen writer (runtime fallback).
+ /// System.Object properties use value.GetType() for runtime type dispatch.
///
private static void EmitScanComplexRuntime(StringBuilder sb, PropInfo p, string a, string i)
{
var childVar = $"sc_{p.Name}";
sb.AppendLine($"{i}var {childVar} = {a};");
sb.AppendLine($"{i}if ({childVar} != null)");
- sb.AppendLine($"{i} AcBinarySerializer.ScanValueGenerated({childVar}, typeof({p.TypeNameForTypeof}), context, depth + 1);");
+ if (p.IsObjectDeclaredType)
+ sb.AppendLine($"{i} AcBinarySerializer.ScanValueGenerated({childVar}, {childVar}.GetType(), context, depth + 1);");
+ else
+ sb.AppendLine($"{i} AcBinarySerializer.ScanValueGenerated({childVar}, typeof({p.TypeNameForTypeof}), context, depth + 1);");
}
///
@@ -2046,6 +2067,8 @@ internal sealed class PropInfo
///
public int InterningFlags { get; }
+ /// True when declared property type is System.Object. Runtime type dispatch needed.
+ public bool IsObjectDeclaredType { get; }
/// True if the Complex property type has [AcBinarySerializable] → has a generated writer.
public bool HasGeneratedWriter { get; }
/// True if the Complex property type implements IId<T> → needs ref tracking in write pass.
@@ -2106,6 +2129,7 @@ internal sealed class PropInfo
public bool ElementNeedsScan => ElementNeedsIdScan || ElementNeedsAllRefScan || ElementNeedsInternScan;
public PropInfo(string n, string tn, string tnForTypeof, PropertyTypeKind tk, bool nullable,
+ bool isObjectDeclaredType = false,
bool? stringInternAttr = null, bool hasGeneratedWriter = false, bool isIId = false, string? writerClassName = null, string? idTypeName = null,
PropertyTypeKind elementKind = PropertyTypeKind.Unknown, bool elementHasGenWriter = false, bool elementIsIId = false,
string? elementWriterClassName = null, string? elementIdTypeName = null, string? collectionKind = null, string? elementFullTypeName = null,
@@ -2120,6 +2144,7 @@ internal sealed class PropInfo
TypeNameForTypeof = tnForTypeof;
TypeKind = tk;
IsNullable = nullable;
+ IsObjectDeclaredType = isObjectDeclaredType;
HasGeneratedWriter = hasGeneratedWriter;
IsIId = isIId;
WriterClassName = writerClassName;
diff --git a/AyCode.Core.Tests/Serialization/AcBinarySerializerIIdReferenceTests.cs b/AyCode.Core.Tests/Serialization/AcBinarySerializerIIdReferenceTests.cs
index 0253c96..d3926d4 100644
--- a/AyCode.Core.Tests/Serialization/AcBinarySerializerIIdReferenceTests.cs
+++ b/AyCode.Core.Tests/Serialization/AcBinarySerializerIIdReferenceTests.cs
@@ -114,9 +114,10 @@ public class AcBinarySerializerIIdReferenceTests
]
};
- //order.Parent = order.Items[0];
- order.Parent = userPreferences;
- order.Items[0].ParentOrder = order;
+ if (mode != ReferenceHandlingMode.None) order.Parent = order.Items[1];
+ else order.Parent = userPreferences;
+
+ order.Items[1].ParentOrder = order;
var options = new AcBinarySerializerOptions { ReferenceHandling = mode };
diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs
index 4f4d363..6b01f75 100644
--- a/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs
+++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs
@@ -84,7 +84,7 @@ public sealed class AcBinarySerializerOptions : AcSerializerOptions
///
public bool UseMetadata { get; set; } = false;
- public bool UseGeneratedCode { get; set; } = false;
+ public bool UseGeneratedCode { get; set; } = true;
///
/// Controls how string interning is applied during serialization.