diff --git a/AyCode.Benchmark/AyCode.Benchmark.csproj b/AyCode.Benchmark/AyCode.Benchmark.csproj
index 346759e..473f685 100644
--- a/AyCode.Benchmark/AyCode.Benchmark.csproj
+++ b/AyCode.Benchmark/AyCode.Benchmark.csproj
@@ -28,6 +28,10 @@
+
+
diff --git a/AyCode.Benchmark/SourceGeneratorBenchmarks.cs b/AyCode.Benchmark/SourceGeneratorBenchmarks.cs
new file mode 100644
index 0000000..1b30b49
--- /dev/null
+++ b/AyCode.Benchmark/SourceGeneratorBenchmarks.cs
@@ -0,0 +1,294 @@
+using AyCode.Core.Serializers.Attributes;
+using AyCode.Core.Serializers.Binaries;
+using BenchmarkDotNet.Attributes;
+using MessagePack;
+using MessagePack.Resolvers;
+
+namespace AyCode.Core.Benchmarks;
+
+///
+/// Pure contractless model - NO MessagePack attributes.
+/// This tests TRUE runtime serialization without any source generation.
+///
+[AcBinarySerializable]
+public class PureContractlessModel
+{
+ public int Id { get; set; }
+ public string Name { get; set; } = "";
+ public string Description { get; set; } = "";
+ public bool IsActive { get; set; }
+ public double Value { get; set; }
+ public decimal Price { get; set; }
+ public long BigNumber { get; set; }
+ public DateTime CreatedAt { get; set; }
+ public Guid UniqueId { get; set; }
+ public int Count { get; set; }
+ public string Category { get; set; } = "";
+ public string Status { get; set; } = "";
+}
+
+///
+/// Benchmark model with only primitive types - fully supported by Source Generator.
+/// MessagePack attributes added for fair comparison with Source Generator.
+///
+[AcBinarySerializable]
+[MessagePackObject]
+public class PrimitiveBenchmarkModel
+{
+ [Key(0)] public int Id { get; set; }
+ [Key(1)] public string Name { get; set; } = "";
+ [Key(2)] public string Description { get; set; } = "";
+ [Key(3)] public bool IsActive { get; set; }
+ [Key(4)] public double Value { get; set; }
+ [Key(5)] public decimal Price { get; set; }
+ [Key(6)] public long BigNumber { get; set; }
+ [Key(7)] public DateTime CreatedAt { get; set; }
+ [Key(8)] public Guid UniqueId { get; set; }
+ [Key(9)] public int Count { get; set; }
+ [Key(10)] public string Category { get; set; } = "";
+ [Key(11)] public string Status { get; set; } = "";
+}
+
+///
+/// TRUE Contractless benchmark - no attributes, pure runtime serialization.
+///
+[ShortRunJob]
+[MemoryDiagnoser]
+[RankColumn]
+public class PureContractlessBenchmark
+{
+ private PureContractlessModel _testData = null!;
+ private byte[] _acBinaryData = null!;
+ private byte[] _msgPackContractlessData = null!;
+
+ private AcBinarySerializerOptions _binaryOptions = null!;
+ private MessagePackSerializerOptions _msgPackContractlessOptions = null!;
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ _testData = new PureContractlessModel
+ {
+ Id = 12345,
+ Name = "Test Product Name",
+ Description = "This is a longer description for testing string serialization performance",
+ IsActive = true,
+ Value = 123.456789,
+ Price = 99.99m,
+ BigNumber = 9876543210L,
+ CreatedAt = DateTime.UtcNow,
+ UniqueId = Guid.NewGuid(),
+ Count = 42,
+ Category = "Electronics",
+ Status = "Available"
+ };
+
+ _binaryOptions = AcBinarySerializerOptions.WithoutReferenceHandling();
+ _msgPackContractlessOptions = ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None);
+
+ _acBinaryData = AcBinarySerializer.Serialize(_testData, _binaryOptions);
+ _msgPackContractlessData = MessagePackSerializer.Serialize(_testData, _msgPackContractlessOptions);
+
+ Console.WriteLine($"=== Pure Contractless (NO attributes) ===");
+ Console.WriteLine($"AcBinary: {_acBinaryData.Length} bytes");
+ Console.WriteLine($"MsgPack Contractless: {_msgPackContractlessData.Length} bytes");
+ }
+
+ [Benchmark(Description = "AcBinary Serialize")]
+ public byte[] Serialize_AcBinary()
+ => AcBinarySerializer.Serialize(_testData, _binaryOptions);
+
+ [Benchmark(Description = "MsgPack Contractless Serialize", Baseline = true)]
+ public byte[] Serialize_MsgPack_Contractless()
+ => MessagePackSerializer.Serialize(_testData, _msgPackContractlessOptions);
+
+ [Benchmark(Description = "AcBinary Deserialize")]
+ public PureContractlessModel? Deserialize_AcBinary()
+ => AcBinaryDeserializer.Deserialize(_acBinaryData);
+
+ [Benchmark(Description = "MsgPack Contractless Deserialize")]
+ public PureContractlessModel? Deserialize_MsgPack_Contractless()
+ => MessagePackSerializer.Deserialize(_msgPackContractlessData, _msgPackContractlessOptions);
+}
+
+///
+/// Benchmark comparing Source Generator vs Runtime serialization for primitive-only types.
+/// Uses MessagePack with [MessagePackObject] attributes for fair Source Generator comparison.
+///
+[ShortRunJob]
+[MemoryDiagnoser]
+[RankColumn]
+public class SourceGeneratorVsRuntimeBenchmark
+{
+ private PrimitiveBenchmarkModel _testData = null!;
+ private byte[] _runtimeSerializedData = null!;
+ private byte[] _msgPackData = null!;
+ private byte[] _msgPackContractlessData = null!;
+
+ private AcBinarySerializerOptions _binaryOptions = null!;
+ private MessagePackSerializerOptions _msgPackOptions = null!;
+ private MessagePackSerializerOptions _msgPackContractlessOptions = null!;
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ _testData = new PrimitiveBenchmarkModel
+ {
+ Id = 12345,
+ Name = "Test Product Name",
+ Description = "This is a longer description for testing string serialization performance",
+ IsActive = true,
+ Value = 123.456789,
+ Price = 99.99m,
+ BigNumber = 9876543210L,
+ CreatedAt = DateTime.UtcNow,
+ UniqueId = Guid.NewGuid(),
+ Count = 42,
+ Category = "Electronics",
+ Status = "Available"
+ };
+
+ _binaryOptions = AcBinarySerializerOptions.WithoutReferenceHandling();
+
+ // MessagePack with Source Generator (uses [MessagePackObject] + [Key] attributes)
+ _msgPackOptions = MessagePackSerializerOptions.Standard;
+
+ // MessagePack without Source Generator (Contractless - reflection based, like AcBinary runtime)
+ _msgPackContractlessOptions = ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None);
+
+ // Pre-serialize for deserialize benchmarks
+ _runtimeSerializedData = AcBinarySerializer.Serialize(_testData, _binaryOptions);
+ _msgPackData = MessagePackSerializer.Serialize(_testData, _msgPackOptions);
+ _msgPackContractlessData = MessagePackSerializer.Serialize(_testData, _msgPackContractlessOptions);
+
+ // Print sizes
+ Console.WriteLine($"=== Primitive Model Serialization ===");
+ Console.WriteLine($"AcBinary Runtime: {_runtimeSerializedData.Length} bytes");
+ Console.WriteLine($"MessagePack SourceGen: {_msgPackData.Length} bytes");
+ Console.WriteLine($"MessagePack Contractless:{_msgPackContractlessData.Length} bytes");
+
+ // Verify generated serializer exists
+ var generatedType = typeof(PrimitiveBenchmarkModel).Assembly.GetType(
+ $"{typeof(PrimitiveBenchmarkModel).FullName}_AcBinarySerializer");
+ Console.WriteLine($"AcBinary Generated serializer found: {generatedType != null}");
+ }
+
+ #region Serialize Benchmarks
+
+ [Benchmark(Description = "AcBinary Runtime Serialize")]
+ public byte[] Serialize_AcBinary_Runtime()
+ => AcBinarySerializer.Serialize(_testData, _binaryOptions);
+
+ [Benchmark(Description = "MsgPack SourceGen Serialize", Baseline = true)]
+ public byte[] Serialize_MsgPack_SourceGen()
+ => MessagePackSerializer.Serialize(_testData, _msgPackOptions);
+
+ [Benchmark(Description = "MsgPack Contractless Serialize")]
+ public byte[] Serialize_MsgPack_Contractless()
+ => MessagePackSerializer.Serialize(_testData, _msgPackContractlessOptions);
+
+ #endregion
+
+ #region Deserialize Benchmarks
+
+ [Benchmark(Description = "AcBinary Runtime Deserialize")]
+ public PrimitiveBenchmarkModel? Deserialize_AcBinary_Runtime()
+ => AcBinaryDeserializer.Deserialize(_runtimeSerializedData);
+
+ [Benchmark(Description = "MsgPack SourceGen Deserialize")]
+ public PrimitiveBenchmarkModel? Deserialize_MsgPack_SourceGen()
+ => MessagePackSerializer.Deserialize(_msgPackData, _msgPackOptions);
+
+ [Benchmark(Description = "MsgPack Contractless Deserialize")]
+ public PrimitiveBenchmarkModel? Deserialize_MsgPack_Contractless()
+ => MessagePackSerializer.Deserialize(_msgPackContractlessData, _msgPackContractlessOptions);
+
+ #endregion
+}
+
+///
+/// Repeated string benchmark model - tests string interning performance.
+/// MessagePack attributes added for fair comparison.
+///
+[AcBinarySerializable]
+[MessagePackObject]
+public class RepeatedStringBenchmarkModel
+{
+ [Key(0)] public int Id { get; set; }
+ [Key(1)] public string Status { get; set; } = "";
+ [Key(2)] public string Category { get; set; } = "";
+ [Key(3)] public string Priority { get; set; } = "";
+ [Key(4)] public string Type { get; set; } = "";
+}
+
+///
+/// Benchmark for types with repeated string values - where AcBinary string interning helps.
+/// Compares against both MessagePack SourceGen and Contractless modes.
+///
+[ShortRunJob]
+[MemoryDiagnoser]
+[RankColumn]
+public class RepeatedStringBenchmark
+{
+ private List _items = null!;
+ private byte[] _acBinaryData = null!;
+ private byte[] _msgPackData = null!;
+ private byte[] _msgPackContractlessData = null!;
+
+ private AcBinarySerializerOptions _binaryOptions = null!;
+ private MessagePackSerializerOptions _msgPackOptions = null!;
+ private MessagePackSerializerOptions _msgPackContractlessOptions = null!;
+
+ [Params(100, 500)]
+ public int ItemCount { get; set; }
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ _items = Enumerable.Range(0, ItemCount).Select(i => new RepeatedStringBenchmarkModel
+ {
+ Id = i,
+ Status = i % 3 == 0 ? "Pending" : i % 3 == 1 ? "Processing" : "Completed",
+ Category = $"Category_{i % 5}",
+ Priority = i % 2 == 0 ? "High" : "Low",
+ Type = i % 4 == 0 ? "TypeA" : i % 4 == 1 ? "TypeB" : i % 4 == 2 ? "TypeC" : "TypeD"
+ }).ToList();
+
+ _binaryOptions = AcBinarySerializerOptions.WithoutReferenceHandling();
+ _msgPackOptions = MessagePackSerializerOptions.Standard;
+ _msgPackContractlessOptions = ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.None);
+
+ _acBinaryData = AcBinarySerializer.Serialize(_items, _binaryOptions);
+ _msgPackData = MessagePackSerializer.Serialize(_items, _msgPackOptions);
+ _msgPackContractlessData = MessagePackSerializer.Serialize(_items, _msgPackContractlessOptions);
+
+ Console.WriteLine($"=== Repeated Strings ({ItemCount} items) ===");
+ Console.WriteLine($"AcBinary: {_acBinaryData.Length} bytes");
+ Console.WriteLine($"MsgPack SourceGen: {_msgPackData.Length} bytes");
+ Console.WriteLine($"MsgPack Contractless: {_msgPackContractlessData.Length} bytes");
+ }
+
+ [Benchmark(Description = "AcBinary Serialize")]
+ public byte[] Serialize_AcBinary()
+ => AcBinarySerializer.Serialize(_items, _binaryOptions);
+
+ [Benchmark(Description = "MsgPack SourceGen Serialize", Baseline = true)]
+ public byte[] Serialize_MsgPack_SourceGen()
+ => MessagePackSerializer.Serialize(_items, _msgPackOptions);
+
+ [Benchmark(Description = "MsgPack Contractless Serialize")]
+ public byte[] Serialize_MsgPack_Contractless()
+ => MessagePackSerializer.Serialize(_items, _msgPackContractlessOptions);
+
+ [Benchmark(Description = "AcBinary Deserialize")]
+ public List? Deserialize_AcBinary()
+ => AcBinaryDeserializer.Deserialize>(_acBinaryData);
+
+ [Benchmark(Description = "MsgPack SourceGen Deserialize")]
+ public List? Deserialize_MsgPack_SourceGen()
+ => MessagePackSerializer.Deserialize>(_msgPackData, _msgPackOptions);
+
+ [Benchmark(Description = "MsgPack Contractless Deserialize")]
+ public List? Deserialize_MsgPack_Contractless()
+ => MessagePackSerializer.Deserialize>(_msgPackContractlessData, _msgPackContractlessOptions);
+}
diff --git a/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs b/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs
new file mode 100644
index 0000000..839dd44
--- /dev/null
+++ b/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs
@@ -0,0 +1,778 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Text;
+
+namespace AyCode.Core.Serializers.SourceGenerator;
+
+///
+/// Source Generator for AcBinary serialization.
+/// Generates optimized serialize/deserialize methods for classes marked with [AcBinarySerializable].
+///
+[Generator]
+public class AcBinarySourceGenerator : IIncrementalGenerator
+{
+ private const string AcBinarySerializableAttributeName = "AyCode.Core.Serializers.Attributes.AcBinarySerializableAttribute";
+
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ // Find all classes with [AcBinarySerializable] attribute
+ var classDeclarations = context.SyntaxProvider
+ .ForAttributeWithMetadataName(
+ AcBinarySerializableAttributeName,
+ predicate: static (node, _) => node is ClassDeclarationSyntax || node is StructDeclarationSyntax,
+ transform: static (ctx, _) => GetClassInfo(ctx))
+ .Where(static info => info != null);
+
+ // Combine with compilation
+ var compilationAndClasses = context.CompilationProvider.Combine(classDeclarations.Collect());
+
+ // Generate source
+ context.RegisterSourceOutput(compilationAndClasses,
+ static (spc, source) => Execute(source.Left, source.Right, spc));
+ }
+
+ private static SerializableClassInfo GetClassInfo(GeneratorAttributeSyntaxContext context)
+ {
+ if (!(context.TargetSymbol is INamedTypeSymbol typeSymbol))
+ return null;
+
+ var namespaceName = typeSymbol.ContainingNamespace.IsGlobalNamespace
+ ? string.Empty
+ : typeSymbol.ContainingNamespace.ToDisplayString();
+
+ var className = typeSymbol.Name;
+ var fullTypeName = typeSymbol.ToDisplayString();
+ var isStruct = typeSymbol.IsValueType;
+
+ // Check if this is a nested type
+ var isNestedType = typeSymbol.ContainingType != null;
+
+ // For nested types, we need the full containing type path for method signatures
+ // e.g. "OuterClass.InnerClass" instead of just "InnerClass"
+ var typeNameForSignature = isNestedType
+ ? GetNestedTypeName(typeSymbol)
+ : className;
+
+ // Get all public properties with getter and setter
+ // DUPLICATED LOGIC: Same filtering as TypeMetadataBase.GetSerializableProperties()
+ var properties = new List();
+ foreach (var member in typeSymbol.GetMembers())
+ {
+ if (member is IPropertySymbol p &&
+ p.DeclaredAccessibility == Accessibility.Public &&
+ p.GetMethod != null &&
+ p.SetMethod != null &&
+ !p.IsIndexer &&
+ !p.IsStatic)
+ {
+ properties.Add(new PropertyInfo(
+ p.Name,
+ p.Type.ToDisplayString(),
+ GetPropertyTypeKind(p.Type),
+ p.Type.NullableAnnotation == NullableAnnotation.Annotated || IsNullableValueType(p.Type)));
+ }
+ }
+
+ // DUPLICATED LOGIC: Same ordering as TypeMetadataBase.GetSerializableProperties()
+ properties.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
+
+ return new SerializableClassInfo(namespaceName, className, fullTypeName, isStruct, isNestedType, typeNameForSignature, properties);
+ }
+
+ ///
+ /// Gets the nested type name chain (e.g., "OuterClass.MiddleClass.InnerClass")
+ ///
+ private static string GetNestedTypeName(INamedTypeSymbol typeSymbol)
+ {
+ var parts = new List();
+ var current = typeSymbol;
+
+ while (current != null)
+ {
+ parts.Insert(0, current.Name);
+ current = current.ContainingType;
+ }
+
+ return string.Join(".", parts);
+ }
+
+ private static bool IsNullableValueType(ITypeSymbol type)
+ {
+ return type is INamedTypeSymbol namedType &&
+ namedType.IsGenericType &&
+ namedType.ConstructedFrom.SpecialType == SpecialType.System_Nullable_T;
+ }
+
+ private static PropertyTypeKind GetPropertyTypeKind(ITypeSymbol type)
+ {
+ // Handle nullable value types
+ if (type is INamedTypeSymbol namedType && namedType.IsGenericType &&
+ namedType.ConstructedFrom.SpecialType == SpecialType.System_Nullable_T)
+ {
+ // Get underlying type
+ var underlyingType = namedType.TypeArguments[0];
+ return GetPropertyTypeKindForUnderlying(underlyingType, isNullable: true);
+ }
+
+ return GetPropertyTypeKindForUnderlying(type, isNullable: false);
+ }
+
+ private static PropertyTypeKind GetPropertyTypeKindForUnderlying(ITypeSymbol type, bool isNullable)
+ {
+ switch (type.SpecialType)
+ {
+ case SpecialType.System_String: return PropertyTypeKind.String;
+ case SpecialType.System_Int32: return isNullable ? PropertyTypeKind.NullableInt32 : PropertyTypeKind.Int32;
+ case SpecialType.System_Int64: return isNullable ? PropertyTypeKind.NullableInt64 : PropertyTypeKind.Int64;
+ case SpecialType.System_Int16: return isNullable ? PropertyTypeKind.NullableInt16 : PropertyTypeKind.Int16;
+ case SpecialType.System_Byte: return isNullable ? PropertyTypeKind.NullableByte : PropertyTypeKind.Byte;
+ case SpecialType.System_SByte: return isNullable ? PropertyTypeKind.NullableSByte : PropertyTypeKind.SByte;
+ case SpecialType.System_UInt16: return isNullable ? PropertyTypeKind.NullableUInt16 : PropertyTypeKind.UInt16;
+ case SpecialType.System_UInt32: return isNullable ? PropertyTypeKind.NullableUInt32 : PropertyTypeKind.UInt32;
+ case SpecialType.System_UInt64: return isNullable ? PropertyTypeKind.NullableUInt64 : PropertyTypeKind.UInt64;
+ case SpecialType.System_Boolean: return isNullable ? PropertyTypeKind.NullableBoolean : PropertyTypeKind.Boolean;
+ case SpecialType.System_Single: return isNullable ? PropertyTypeKind.NullableSingle : PropertyTypeKind.Single;
+ case SpecialType.System_Double: return isNullable ? PropertyTypeKind.NullableDouble : PropertyTypeKind.Double;
+ case SpecialType.System_Decimal: return isNullable ? PropertyTypeKind.NullableDecimal : PropertyTypeKind.Decimal;
+ case SpecialType.System_DateTime: return isNullable ? PropertyTypeKind.NullableDateTime : PropertyTypeKind.DateTime;
+ default: return GetNonSpecialTypeKind(type, isNullable);
+ }
+ }
+
+ private static PropertyTypeKind GetNonSpecialTypeKind(ITypeSymbol type, bool isNullable)
+ {
+ var fullName = type.ToDisplayString();
+
+ if (fullName == "System.Guid") return isNullable ? PropertyTypeKind.NullableGuid : PropertyTypeKind.Guid;
+ if (fullName == "System.TimeSpan") return isNullable ? PropertyTypeKind.NullableTimeSpan : PropertyTypeKind.TimeSpan;
+ if (fullName == "System.DateTimeOffset") return isNullable ? PropertyTypeKind.NullableDateTimeOffset : PropertyTypeKind.DateTimeOffset;
+ if (fullName == "System.DateOnly") return isNullable ? PropertyTypeKind.NullableDateOnly : PropertyTypeKind.DateOnly;
+ if (fullName == "System.TimeOnly") return isNullable ? PropertyTypeKind.NullableTimeOnly : PropertyTypeKind.TimeOnly;
+ if (type.TypeKind == TypeKind.Enum) return isNullable ? PropertyTypeKind.NullableEnum : PropertyTypeKind.Enum;
+ if (IsCollectionType(type)) return PropertyTypeKind.Collection;
+ if (type.TypeKind == TypeKind.Class || type.TypeKind == TypeKind.Struct) return PropertyTypeKind.Complex;
+
+ return PropertyTypeKind.Unknown;
+ }
+
+ private static bool IsCollectionType(ITypeSymbol type)
+ {
+ if (type is IArrayTypeSymbol)
+ return true;
+
+ if (type is INamedTypeSymbol namedType)
+ {
+ foreach (var iface in namedType.AllInterfaces)
+ {
+ if (iface.OriginalDefinition.SpecialType == SpecialType.System_Collections_Generic_IEnumerable_T)
+ return true;
+ var ifaceName = iface.ToDisplayString();
+ if (ifaceName.StartsWith("System.Collections.Generic.IList<") ||
+ ifaceName.StartsWith("System.Collections.Generic.ICollection<"))
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static void Execute(Compilation compilation, ImmutableArray classes, SourceProductionContext context)
+ {
+ if (classes.IsDefaultOrEmpty)
+ return;
+
+ foreach (var classInfo in classes)
+ {
+ if (classInfo == null)
+ continue;
+
+ var source = GenerateSerializerClass(classInfo);
+ context.AddSource($"{classInfo.ClassName}_AcBinarySerializer.g.cs", SourceText.From(source, Encoding.UTF8));
+ }
+ }
+
+ private static string GenerateSerializerClass(SerializableClassInfo classInfo)
+ {
+ var sb = new StringBuilder();
+
+ sb.AppendLine("// ");
+ sb.AppendLine("#nullable enable");
+ sb.AppendLine();
+ sb.AppendLine("using System;");
+ sb.AppendLine("using System.Runtime.CompilerServices;");
+ sb.AppendLine("using AyCode.Core.Serializers.Binaries;");
+ sb.AppendLine();
+
+ if (!string.IsNullOrEmpty(classInfo.Namespace))
+ {
+ sb.AppendLine($"namespace {classInfo.Namespace}");
+ sb.AppendLine("{");
+ }
+
+ var indent = string.IsNullOrEmpty(classInfo.Namespace) ? "" : " ";
+
+ sb.AppendLine($"{indent}/// ");
+ sb.AppendLine($"{indent}/// Generated binary serializer for {classInfo.ClassName}.");
+ sb.AppendLine($"{indent}/// ");
+ sb.AppendLine($"{indent}internal static class {classInfo.ClassName}_AcBinarySerializer");
+ sb.AppendLine($"{indent}{{");
+
+ // Generate property count constant
+ sb.AppendLine($"{indent} public const int PropertyCount = {classInfo.Properties.Count};");
+ sb.AppendLine();
+
+ // Generate property names array for validation
+ sb.AppendLine($"{indent} /// ");
+ sb.AppendLine($"{indent} /// Property names in serialization order (alphabetical).");
+ sb.AppendLine($"{indent} /// Used for runtime validation against TypeMetadataBase.GetSerializableProperties().");
+ sb.AppendLine($"{indent} /// ");
+ sb.Append($"{indent} public static readonly string[] PropertyNames = new[] {{ ");
+ sb.Append(string.Join(", ", classInfo.Properties.Select(p => $"\"{p.Name}\"")));
+ sb.AppendLine(" };");
+ sb.AppendLine();
+
+ // Generate Serialize method
+ GenerateSerializeMethod(sb, classInfo, indent);
+
+ // Generate Deserialize method
+ GenerateDeserializeMethod(sb, classInfo, indent);
+
+ sb.AppendLine($"{indent}}}");
+
+ if (!string.IsNullOrEmpty(classInfo.Namespace))
+ {
+ sb.AppendLine("}");
+ }
+
+ return sb.ToString();
+ }
+
+ private static void GenerateSerializeMethod(StringBuilder sb, SerializableClassInfo classInfo, string indent)
+ {
+ sb.AppendLine($"{indent} /// ");
+ sb.AppendLine($"{indent} /// Serializes a {classInfo.ClassName} instance to the binary context.");
+ sb.AppendLine($"{indent} /// Direct property access - no reflection, no boxing for primitives.");
+ sb.AppendLine($"{indent} /// ");
+ sb.AppendLine($"{indent} [MethodImpl(MethodImplOptions.AggressiveInlining)]");
+ sb.AppendLine($"{indent} public static void Serialize({classInfo.TypeNameForSignature} obj, ref AcBinarySerializer.BinarySerializationContext context)");
+ sb.AppendLine($"{indent} {{");
+
+ foreach (var prop in classInfo.Properties)
+ {
+ GenerateSerializeProperty(sb, prop, indent + " ");
+ }
+
+ sb.AppendLine($"{indent} }}");
+ sb.AppendLine();
+ }
+
+ private static void GenerateSerializeProperty(StringBuilder sb, PropertyInfo prop, string indent)
+ {
+ var propAccess = $"obj.{prop.Name}";
+
+ // Handle nullable VALUE types (Nullable) - these use .HasValue and .Value
+ if (IsNullableValueTypeKind(prop.TypeKind))
+ {
+ sb.AppendLine($"{indent}// {prop.Name}: {prop.TypeName} (nullable value type)");
+ sb.AppendLine($"{indent}if ({propAccess}.HasValue)");
+ sb.AppendLine($"{indent}{{");
+ GenerateSerializeValue(sb, prop.TypeKind, $"{propAccess}.Value", indent + " ");
+ sb.AppendLine($"{indent}}}");
+ sb.AppendLine($"{indent}else");
+ sb.AppendLine($"{indent}{{");
+ sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.Null);");
+ sb.AppendLine($"{indent}}}");
+ return;
+ }
+
+ sb.AppendLine($"{indent}// {prop.Name}: {prop.TypeName}");
+
+ // String needs null check
+ if (prop.TypeKind == PropertyTypeKind.String)
+ {
+ sb.AppendLine($"{indent}if ({propAccess} == null)");
+ sb.AppendLine($"{indent}{{");
+ sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.Null);");
+ sb.AppendLine($"{indent}}}");
+ sb.AppendLine($"{indent}else if ({propAccess}.Length == 0)");
+ sb.AppendLine($"{indent}{{");
+ sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.StringEmpty);");
+ sb.AppendLine($"{indent}}}");
+ sb.AppendLine($"{indent}else");
+ sb.AppendLine($"{indent}{{");
+ sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.String);");
+ sb.AppendLine($"{indent} context.WriteStringUtf8({propAccess});");
+ sb.AppendLine($"{indent}}}");
+ return;
+ }
+
+ // Nullable reference types (Complex/Collection with ? annotation) - use == null
+ if (prop.IsNullable && (prop.TypeKind == PropertyTypeKind.Complex || prop.TypeKind == PropertyTypeKind.Collection))
+ {
+ sb.AppendLine($"{indent}if ({propAccess} == null)");
+ sb.AppendLine($"{indent}{{");
+ sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.Null);");
+ sb.AppendLine($"{indent}}}");
+ sb.AppendLine($"{indent}else");
+ sb.AppendLine($"{indent}{{");
+ GenerateSerializeValue(sb, prop.TypeKind, propAccess, indent + " ");
+ sb.AppendLine($"{indent}}}");
+ return;
+ }
+
+ GenerateSerializeValue(sb, prop.TypeKind, propAccess, indent);
+ }
+
+ ///
+ /// Checks if the type kind represents a nullable VALUE type (Nullable<T>), not a reference type
+ ///
+ private static bool IsNullableValueTypeKind(PropertyTypeKind kind)
+ {
+ return kind >= PropertyTypeKind.NullableInt32 && kind <= PropertyTypeKind.NullableEnum;
+ }
+
+ private static void GenerateSerializeValue(StringBuilder sb, PropertyTypeKind typeKind, string valueExpr, string indent)
+ {
+ switch (typeKind)
+ {
+ case PropertyTypeKind.Int32:
+ case PropertyTypeKind.NullableInt32:
+ sb.AppendLine($"{indent}if (BinaryTypeCode.TryEncodeTinyInt({valueExpr}, out var tiny_{valueExpr.Replace(".", "_")}))");
+ sb.AppendLine($"{indent}{{");
+ sb.AppendLine($"{indent} context.WriteByte(tiny_{valueExpr.Replace(".", "_")});");
+ sb.AppendLine($"{indent}}}");
+ sb.AppendLine($"{indent}else");
+ sb.AppendLine($"{indent}{{");
+ sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.Int32);");
+ sb.AppendLine($"{indent} context.WriteVarInt({valueExpr});");
+ sb.AppendLine($"{indent}}}");
+ break;
+
+ case PropertyTypeKind.Int64:
+ case PropertyTypeKind.NullableInt64:
+ sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.Int64);");
+ sb.AppendLine($"{indent}context.WriteVarLong({valueExpr});");
+ break;
+
+ case PropertyTypeKind.Boolean:
+ case PropertyTypeKind.NullableBoolean:
+ sb.AppendLine($"{indent}context.WriteByte({valueExpr} ? BinaryTypeCode.True : BinaryTypeCode.False);");
+ break;
+
+ case PropertyTypeKind.Double:
+ case PropertyTypeKind.NullableDouble:
+ sb.AppendLine($"{indent}context.WriteTypeCodeAndRaw(BinaryTypeCode.Float64, {valueExpr});");
+ break;
+
+ case PropertyTypeKind.Single:
+ case PropertyTypeKind.NullableSingle:
+ sb.AppendLine($"{indent}context.WriteTypeCodeAndRaw(BinaryTypeCode.Float32, {valueExpr});");
+ break;
+
+ case PropertyTypeKind.Decimal:
+ case PropertyTypeKind.NullableDecimal:
+ sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.Decimal);");
+ sb.AppendLine($"{indent}context.WriteDecimalBits({valueExpr});");
+ break;
+
+ case PropertyTypeKind.DateTime:
+ case PropertyTypeKind.NullableDateTime:
+ sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.DateTime);");
+ sb.AppendLine($"{indent}context.WriteDateTimeBits({valueExpr});");
+ break;
+
+ case PropertyTypeKind.Guid:
+ case PropertyTypeKind.NullableGuid:
+ sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.Guid);");
+ sb.AppendLine($"{indent}context.WriteGuidBits({valueExpr});");
+ break;
+
+ case PropertyTypeKind.Byte:
+ case PropertyTypeKind.NullableByte:
+ sb.AppendLine($"{indent}context.WriteTwoBytes(BinaryTypeCode.UInt8, {valueExpr});");
+ break;
+
+ case PropertyTypeKind.Int16:
+ case PropertyTypeKind.NullableInt16:
+ sb.AppendLine($"{indent}context.WriteTypeCodeAndRaw(BinaryTypeCode.Int16, {valueExpr});");
+ break;
+
+ case PropertyTypeKind.UInt16:
+ case PropertyTypeKind.NullableUInt16:
+ sb.AppendLine($"{indent}context.WriteTypeCodeAndRaw(BinaryTypeCode.UInt16, {valueExpr});");
+ break;
+
+ case PropertyTypeKind.UInt32:
+ case PropertyTypeKind.NullableUInt32:
+ sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.UInt32);");
+ sb.AppendLine($"{indent}context.WriteVarUInt({valueExpr});");
+ sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.UInt32);");
+ sb.AppendLine($"{indent}context.WriteVarUInt({valueExpr});");
+ break;
+
+ case PropertyTypeKind.UInt64:
+ case PropertyTypeKind.NullableUInt64:
+ sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.UInt64);");
+ sb.AppendLine($"{indent}context.WriteVarULong({valueExpr});");
+ break;
+
+ case PropertyTypeKind.TimeSpan:
+ case PropertyTypeKind.NullableTimeSpan:
+ sb.AppendLine($"{indent}context.WriteTypeCodeAndRaw(BinaryTypeCode.TimeSpan, {valueExpr}.Ticks);");
+ break;
+
+ case PropertyTypeKind.DateTimeOffset:
+ case PropertyTypeKind.NullableDateTimeOffset:
+ sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.DateTimeOffset);");
+ sb.AppendLine($"{indent}context.WriteDateTimeOffsetBits({valueExpr});");
+ break;
+
+ case PropertyTypeKind.Enum:
+ case PropertyTypeKind.NullableEnum:
+ sb.AppendLine($"{indent}context.WriteByte(BinaryTypeCode.Enum);");
+ sb.AppendLine($"{indent}var enumVal = (int){valueExpr};");
+ sb.AppendLine($"{indent}if (BinaryTypeCode.TryEncodeTinyInt(enumVal, out var tinyEnum))");
+ sb.AppendLine($"{indent}{{");
+ sb.AppendLine($"{indent} context.WriteByte(tinyEnum);");
+ sb.AppendLine($"{indent}}}");
+ sb.AppendLine($"{indent}else");
+ sb.AppendLine($"{indent}{{");
+ sb.AppendLine($"{indent} context.WriteByte(BinaryTypeCode.Int32);");
+ sb.AppendLine($"{indent} context.WriteVarInt(enumVal);");
+ sb.AppendLine($"{indent}}}");
+ break;
+
+ case PropertyTypeKind.Collection:
+ case PropertyTypeKind.Complex:
+ // TODO: Collections and complex types will be implemented later
+ sb.AppendLine($"{indent}// TODO: Complex/Collection type - fallback to runtime serializer");
+ sb.AppendLine($"{indent}throw new NotImplementedException(\"Complex/Collection types not yet implemented in generated serializer\");");
+ break;
+
+ default:
+ sb.AppendLine($"{indent}// Unknown type - fallback needed");
+ sb.AppendLine($"{indent}throw new NotImplementedException($\"Type {typeKind} not implemented in generated serializer\");");
+ break;
+ }
+ }
+
+ private static void GenerateDeserializeMethod(StringBuilder sb, SerializableClassInfo classInfo, string indent)
+ {
+ sb.AppendLine($"{indent} /// ");
+ sb.AppendLine($"{indent} /// Deserializes properties into a {classInfo.ClassName} instance from the binary context.");
+ sb.AppendLine($"{indent} /// Direct property access - no reflection, no boxing for primitives.");
+ sb.AppendLine($"{indent} /// ");
+ sb.AppendLine($"{indent} [MethodImpl(MethodImplOptions.AggressiveInlining)]");
+ sb.AppendLine($"{indent} public static void Deserialize({classInfo.TypeNameForSignature} obj, ref AcBinaryDeserializer.BinaryDeserializationContext context)");
+ sb.AppendLine($"{indent} {{");
+
+ foreach (var prop in classInfo.Properties)
+ {
+ GenerateDeserializeProperty(sb, prop, indent + " ");
+ }
+
+ sb.AppendLine($"{indent} }}");
+ }
+
+ private static void GenerateDeserializeProperty(StringBuilder sb, PropertyInfo prop, string indent)
+ {
+ sb.AppendLine($"{indent}// {prop.Name}: {prop.TypeName}");
+ sb.AppendLine($"{indent}{{");
+ sb.AppendLine($"{indent} var peekCode = context.PeekByte();");
+
+ // Handle Skip marker
+ sb.AppendLine($"{indent} if (peekCode == BinaryTypeCode.PropertySkip)");
+ sb.AppendLine($"{indent} {{");
+ sb.AppendLine($"{indent} context.ReadByte(); // consume Skip marker");
+ sb.AppendLine($"{indent} // Property keeps default value");
+ sb.AppendLine($"{indent} }}");
+
+ // Handle Null marker
+ sb.AppendLine($"{indent} else if (peekCode == BinaryTypeCode.Null)");
+ sb.AppendLine($"{indent} {{");
+ sb.AppendLine($"{indent} context.ReadByte(); // consume Null marker");
+ if (prop.IsNullable || prop.TypeKind == PropertyTypeKind.String)
+ {
+ sb.AppendLine($"{indent} obj.{prop.Name} = default;");
+ }
+ else
+ {
+ sb.AppendLine($"{indent} // Non-nullable property, keep default");
+ }
+ sb.AppendLine($"{indent} }}");
+
+ // Handle actual value
+ sb.AppendLine($"{indent} else");
+ sb.AppendLine($"{indent} {{");
+ GenerateDeserializeValue(sb, prop, indent + " ");
+ sb.AppendLine($"{indent} }}");
+
+ sb.AppendLine($"{indent}}}");
+ }
+
+ private static void GenerateDeserializeValue(StringBuilder sb, PropertyInfo prop, string indent)
+ {
+ var propAccess = $"obj.{prop.Name}";
+
+ switch (prop.TypeKind)
+ {
+ case PropertyTypeKind.String:
+ sb.AppendLine($"{indent}if (peekCode == BinaryTypeCode.StringEmpty)");
+ sb.AppendLine($"{indent}{{");
+ sb.AppendLine($"{indent} context.ReadByte();");
+ sb.AppendLine($"{indent} {propAccess} = string.Empty;");
+ sb.AppendLine($"{indent}}}");
+ sb.AppendLine($"{indent}else if (peekCode == BinaryTypeCode.String)");
+ sb.AppendLine($"{indent}{{");
+ sb.AppendLine($"{indent} context.ReadByte();");
+ sb.AppendLine($"{indent} var len = (int)context.ReadVarUInt();");
+ sb.AppendLine($"{indent} {propAccess} = context.ReadStringUtf8(len);");
+ sb.AppendLine($"{indent}}}");
+ sb.AppendLine($"{indent}else if (BinaryTypeCode.IsFixStr(peekCode))");
+ sb.AppendLine($"{indent}{{");
+ sb.AppendLine($"{indent} context.ReadByte();");
+ sb.AppendLine($"{indent} var len = BinaryTypeCode.DecodeFixStrLength(peekCode);");
+ sb.AppendLine($"{indent} {propAccess} = len == 0 ? string.Empty : context.ReadStringUtf8(len);");
+ sb.AppendLine($"{indent}}}");
+ break;
+
+ case PropertyTypeKind.Int32:
+ case PropertyTypeKind.NullableInt32:
+ sb.AppendLine($"{indent}if (BinaryTypeCode.IsTinyInt(peekCode))");
+ sb.AppendLine($"{indent}{{");
+ sb.AppendLine($"{indent} context.ReadByte();");
+ sb.AppendLine($"{indent} {propAccess} = BinaryTypeCode.DecodeTinyInt(peekCode);");
+ sb.AppendLine($"{indent}}}");
+ sb.AppendLine($"{indent}else if (peekCode == BinaryTypeCode.Int32)");
+ sb.AppendLine($"{indent}{{");
+ sb.AppendLine($"{indent} context.ReadByte();");
+ sb.AppendLine($"{indent} {propAccess} = context.ReadVarInt();");
+ sb.AppendLine($"{indent}}}");
+ break;
+
+ case PropertyTypeKind.Int64:
+ case PropertyTypeKind.NullableInt64:
+ sb.AppendLine($"{indent}if (BinaryTypeCode.IsTinyInt(peekCode))");
+ sb.AppendLine($"{indent}{{");
+ sb.AppendLine($"{indent} context.ReadByte();");
+ sb.AppendLine($"{indent} {propAccess} = BinaryTypeCode.DecodeTinyInt(peekCode);");
+ sb.AppendLine($"{indent}}}");
+ sb.AppendLine($"{indent}else if (peekCode == BinaryTypeCode.Int32)");
+ sb.AppendLine($"{indent}{{");
+ sb.AppendLine($"{indent} context.ReadByte();");
+ sb.AppendLine($"{indent} {propAccess} = context.ReadVarInt();");
+ sb.AppendLine($"{indent}}}");
+ sb.AppendLine($"{indent}else if (peekCode == BinaryTypeCode.Int64)");
+ sb.AppendLine($"{indent}{{");
+ sb.AppendLine($"{indent} context.ReadByte();");
+ sb.AppendLine($"{indent} {propAccess} = context.ReadVarLong();");
+ sb.AppendLine($"{indent}}}");
+ break;
+
+ case PropertyTypeKind.Boolean:
+ case PropertyTypeKind.NullableBoolean:
+ sb.AppendLine($"{indent}context.ReadByte();");
+ sb.AppendLine($"{indent}{propAccess} = peekCode == BinaryTypeCode.True;");
+ break;
+
+ case PropertyTypeKind.Double:
+ case PropertyTypeKind.NullableDouble:
+ sb.AppendLine($"{indent}context.ReadByte();");
+ sb.AppendLine($"{indent}{propAccess} = context.ReadDoubleUnsafe();");
+ break;
+
+ case PropertyTypeKind.Single:
+ case PropertyTypeKind.NullableSingle:
+ sb.AppendLine($"{indent}context.ReadByte();");
+ sb.AppendLine($"{indent}{propAccess} = context.ReadSingleUnsafe();");
+ break;
+
+ case PropertyTypeKind.Decimal:
+ case PropertyTypeKind.NullableDecimal:
+ sb.AppendLine($"{indent}context.ReadByte();");
+ sb.AppendLine($"{indent}{propAccess} = context.ReadDecimalUnsafe();");
+ break;
+
+ case PropertyTypeKind.DateTime:
+ case PropertyTypeKind.NullableDateTime:
+ sb.AppendLine($"{indent}context.ReadByte();");
+ sb.AppendLine($"{indent}{propAccess} = context.ReadDateTimeUnsafe();");
+ break;
+
+ case PropertyTypeKind.Guid:
+ case PropertyTypeKind.NullableGuid:
+ sb.AppendLine($"{indent}context.ReadByte();");
+ sb.AppendLine($"{indent}{propAccess} = context.ReadGuidUnsafe();");
+ break;
+
+ case PropertyTypeKind.Byte:
+ case PropertyTypeKind.NullableByte:
+ sb.AppendLine($"{indent}if (BinaryTypeCode.IsTinyInt(peekCode))");
+ sb.AppendLine($"{indent}{{");
+ sb.AppendLine($"{indent} context.ReadByte();");
+ sb.AppendLine($"{indent} {propAccess} = (byte)BinaryTypeCode.DecodeTinyInt(peekCode);");
+ sb.AppendLine($"{indent}}}");
+ sb.AppendLine($"{indent}else");
+ sb.AppendLine($"{indent}{{");
+ sb.AppendLine($"{indent} context.ReadByte();");
+ sb.AppendLine($"{indent} {propAccess} = context.ReadByte();");
+ sb.AppendLine($"{indent}}}");
+ break;
+
+ case PropertyTypeKind.Int16:
+ case PropertyTypeKind.NullableInt16:
+ sb.AppendLine($"{indent}context.ReadByte();");
+ sb.AppendLine($"{indent}{propAccess} = context.ReadInt16Unsafe();");
+ break;
+
+ case PropertyTypeKind.UInt16:
+ case PropertyTypeKind.NullableUInt16:
+ sb.AppendLine($"{indent}context.ReadByte();");
+ sb.AppendLine($"{indent}{propAccess} = context.ReadUInt16Unsafe();");
+ break;
+
+ case PropertyTypeKind.UInt32:
+ case PropertyTypeKind.NullableUInt32:
+ sb.AppendLine($"{indent}context.ReadByte();");
+ sb.AppendLine($"{indent}{propAccess} = context.ReadVarUInt();");
+ break;
+
+ case PropertyTypeKind.UInt64:
+ case PropertyTypeKind.NullableUInt64:
+ sb.AppendLine($"{indent}context.ReadByte();");
+ sb.AppendLine($"{indent}{propAccess} = context.ReadVarULong();");
+ break;
+
+ case PropertyTypeKind.TimeSpan:
+ case PropertyTypeKind.NullableTimeSpan:
+ sb.AppendLine($"{indent}context.ReadByte();");
+ sb.AppendLine($"{indent}{propAccess} = context.ReadTimeSpanUnsafe();");
+ break;
+
+ case PropertyTypeKind.DateTimeOffset:
+ case PropertyTypeKind.NullableDateTimeOffset:
+ sb.AppendLine($"{indent}context.ReadByte();");
+ sb.AppendLine($"{indent}{propAccess} = context.ReadDateTimeOffsetUnsafe();");
+ break;
+
+ case PropertyTypeKind.Enum:
+ case PropertyTypeKind.NullableEnum:
+ sb.AppendLine($"{indent}// TODO: Enum deserialization needs type info");
+ sb.AppendLine($"{indent}throw new NotImplementedException(\"Enum deserialization not yet implemented\");");
+ break;
+
+ case PropertyTypeKind.Collection:
+ case PropertyTypeKind.Complex:
+ sb.AppendLine($"{indent}// TODO: Complex/Collection types");
+ sb.AppendLine($"{indent}throw new NotImplementedException(\"Complex/Collection deserialization not yet implemented\");");
+ break;
+
+ default:
+ sb.AppendLine($"{indent}throw new NotImplementedException($\"Type deserialization not implemented\");");
+ break;
+ }
+ }
+}
+
+///
+/// Information about a class marked with [AcBinarySerializable].
+///
+internal sealed class SerializableClassInfo
+{
+ public string Namespace { get; }
+ public string ClassName { get; }
+ public string FullTypeName { get; }
+ public bool IsStruct { get; }
+ public bool IsNestedType { get; }
+ ///
+ /// The type name to use in method signatures. For nested types this includes the containing types.
+ /// e.g., "OuterClass.InnerClass"
+ ///
+ public string TypeNameForSignature { get; }
+ public List Properties { get; }
+
+ public SerializableClassInfo(string ns, string className, string fullTypeName, bool isStruct, bool isNestedType, string typeNameForSignature, List properties)
+ {
+ Namespace = ns;
+ ClassName = className;
+ FullTypeName = fullTypeName;
+ IsStruct = isStruct;
+ IsNestedType = isNestedType;
+ TypeNameForSignature = typeNameForSignature;
+ Properties = properties;
+ }
+}
+
+///
+/// Information about a serializable property.
+///
+internal sealed class PropertyInfo
+{
+ public string Name { get; }
+ public string TypeName { get; }
+ public PropertyTypeKind TypeKind { get; }
+ public bool IsNullable { get; }
+
+ public PropertyInfo(string name, string typeName, PropertyTypeKind typeKind, bool isNullable)
+ {
+ Name = name;
+ TypeName = typeName;
+ TypeKind = typeKind;
+ IsNullable = isNullable;
+ }
+}
+
+///
+/// Kind of property type for code generation.
+///
+internal enum PropertyTypeKind
+{
+ Unknown,
+ String,
+ Int32,
+ Int64,
+ Int16,
+ Byte,
+ SByte,
+ UInt16,
+ UInt32,
+ UInt64,
+ Boolean,
+ Single,
+ Double,
+ Decimal,
+ DateTime,
+ DateTimeOffset,
+ TimeSpan,
+ DateOnly,
+ TimeOnly,
+ Guid,
+ Enum,
+ Collection,
+ Complex,
+ // Nullable variants
+ NullableInt32,
+ NullableInt64,
+ NullableInt16,
+ NullableByte,
+ NullableSByte,
+ NullableUInt16,
+ NullableUInt32,
+ NullableUInt64,
+ NullableBoolean,
+ NullableSingle,
+ NullableDouble,
+ NullableDecimal,
+ NullableDateTime,
+ NullableDateTimeOffset,
+ NullableTimeSpan,
+ NullableDateOnly,
+ NullableTimeOnly,
+ NullableGuid,
+ NullableEnum
+}
diff --git a/AyCode.Core.Serializers.SourceGenerator/AyCode.Core.Serializers.SourceGenerator.csproj b/AyCode.Core.Serializers.SourceGenerator/AyCode.Core.Serializers.SourceGenerator.csproj
new file mode 100644
index 0000000..453d398
Binary files /dev/null and b/AyCode.Core.Serializers.SourceGenerator/AyCode.Core.Serializers.SourceGenerator.csproj differ
diff --git a/AyCode.Core.Tests/AyCode.Core.Tests.csproj b/AyCode.Core.Tests/AyCode.Core.Tests.csproj
index 9e9f7d9..1bf6239 100644
--- a/AyCode.Core.Tests/AyCode.Core.Tests.csproj
+++ b/AyCode.Core.Tests/AyCode.Core.Tests.csproj
@@ -3,6 +3,9 @@
false
true
+
+ true
+ $(BaseIntermediateOutputPath)Generated
@@ -25,6 +28,9 @@
+
diff --git a/AyCode.Core.Tests/Serialization/GeneratedSerializerIntegrationTests.cs b/AyCode.Core.Tests/Serialization/GeneratedSerializerIntegrationTests.cs
new file mode 100644
index 0000000..ee4a387
--- /dev/null
+++ b/AyCode.Core.Tests/Serialization/GeneratedSerializerIntegrationTests.cs
@@ -0,0 +1,149 @@
+using AyCode.Core.Serializers.Binaries;
+using AyCode.Core.Tests.TestModels;
+
+namespace AyCode.Core.Tests.Serialization;
+
+///
+/// Tests for Source Generator based serialization integration.
+///
+[TestClass]
+public class GeneratedSerializerIntegrationTests
+{
+ [TestMethod]
+ public void GeneratedSerializerType_Exists_ForMarkedTypes()
+ {
+ // Arrange - types marked with [AcBinarySerializable]
+ var type = typeof(GeneratedSerializerTestModel);
+
+ // Act - find the generated serializer type directly
+ var generatedTypeName = $"{type.FullName}_AcBinarySerializer";
+ var serializerType = type.Assembly.GetType(generatedTypeName);
+
+ // Assert
+ Assert.IsNotNull(serializerType,
+ $"Generated serializer type '{generatedTypeName}' should exist for [AcBinarySerializable] marked type");
+ }
+
+ [TestMethod]
+ public void GeneratedSerializerType_HasCorrectMethods()
+ {
+ // Arrange
+ var type = typeof(SimpleGeneratedModel);
+
+ // Act - find the generated serializer type directly
+ var generatedTypeName = $"{type.FullName}_AcBinarySerializer";
+ var serializerType = type.Assembly.GetType(generatedTypeName);
+
+ // Assert
+ Assert.IsNotNull(serializerType, $"Generated serializer type '{generatedTypeName}' should exist");
+
+ var serializeMethod = serializerType.GetMethod("Serialize",
+ System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
+ Assert.IsNotNull(serializeMethod, "Serialize method should exist");
+
+ var deserializeMethod = serializerType.GetMethod("Deserialize",
+ System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
+ Assert.IsNotNull(deserializeMethod, "Deserialize method should exist");
+
+ var propertyNamesField = serializerType.GetField("PropertyNames",
+ System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
+ Assert.IsNotNull(propertyNamesField, "PropertyNames field should exist");
+
+ var propertyNames = propertyNamesField.GetValue(null) as string[];
+ Assert.IsNotNull(propertyNames, "PropertyNames should not be null");
+ Assert.AreEqual(3, propertyNames.Length, "SimpleGeneratedModel has 3 properties");
+
+ // Verify alphabetical order
+ Assert.AreEqual("Age", propertyNames[0]);
+ Assert.AreEqual("FirstName", propertyNames[1]);
+ Assert.AreEqual("LastName", propertyNames[2]);
+ }
+
+ [TestMethod]
+ public void GeneratedSerializerPropertyNames_MatchRuntimeOrder()
+ {
+ // This test verifies that the generated property order matches the runtime serializer's order
+ // This is critical for binary compatibility!
+
+ var type = typeof(GeneratedSerializerTestModel);
+
+ // Get generated property names
+ var generatedTypeName = $"{type.FullName}_AcBinarySerializer";
+ var serializerType = type.Assembly.GetType(generatedTypeName);
+ Assert.IsNotNull(serializerType);
+
+ var propertyNamesField = serializerType.GetField("PropertyNames",
+ System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
+ var generatedNames = propertyNamesField?.GetValue(null) as string[];
+ Assert.IsNotNull(generatedNames);
+
+ // Get runtime property names using the same logic as TypeMetadataBase
+ var runtimeProps = type.GetProperties(
+ System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)
+ .Where(p => p.CanRead && p.CanWrite && !p.GetIndexParameters().Any())
+ .OrderBy(p => p.Name, StringComparer.Ordinal)
+ .Select(p => p.Name)
+ .ToArray();
+
+ // Assert they match
+ CollectionAssert.AreEqual(runtimeProps, generatedNames,
+ "Generated property names must match runtime property order for binary compatibility");
+ }
+
+ [TestMethod]
+ public void Serialization_WorksCorrectly_WithGeneratedSerializerPresent()
+ {
+ // This test ensures that regular serialization still works even when
+ // generated serializers are present (they are not yet integrated into the hot path)
+
+ var original = new GeneratedSerializerTestModel
+ {
+ Id = 42,
+ Name = "Test",
+ IsActive = true,
+ Value = 3.14,
+ Created = new DateTime(2025, 1, 5, 10, 30, 0, DateTimeKind.Utc),
+ UniqueId = Guid.NewGuid(),
+ Price = 99.99m,
+ BigNumber = 9999999999L
+ };
+
+ // Serialize and deserialize using the regular path
+ var bytes = AcBinarySerializer.Serialize(original, AcBinarySerializerOptions.WithoutReferenceHandling());
+ var deserialized = AcBinaryDeserializer.Deserialize(bytes);
+
+ // Assert
+ Assert.IsNotNull(deserialized);
+ Assert.AreEqual(original.Id, deserialized.Id);
+ Assert.AreEqual(original.Name, deserialized.Name);
+ Assert.AreEqual(original.IsActive, deserialized.IsActive);
+ Assert.AreEqual(original.Value, deserialized.Value);
+ Assert.AreEqual(original.Created, deserialized.Created);
+ Assert.AreEqual(original.UniqueId, deserialized.UniqueId);
+ Assert.AreEqual(original.Price, deserialized.Price);
+ Assert.AreEqual(original.BigNumber, deserialized.BigNumber);
+ }
+
+ [TestMethod]
+ public void NestedType_GeneratedSerializer_IsFound()
+ {
+ // Test that nested types (like QuickBenchmark.TestClassWithRepeatedValues)
+ // have their generated serializers properly named and discoverable
+
+ var type = typeof(QuickBenchmark.TestClassWithRepeatedValues);
+ var ns = type.Namespace ?? "";
+
+ // For nested types, the generated class is at namespace level with just the type name
+ var simpleName = $"{(string.IsNullOrEmpty(ns) ? "" : ns + ".")}{type.Name}_AcBinarySerializer";
+ var serializerType = type.Assembly.GetType(simpleName);
+
+ Assert.IsNotNull(serializerType,
+ $"Generated serializer for nested type should be found at '{simpleName}'");
+
+ // Verify it has the expected methods
+ Assert.IsNotNull(serializerType.GetMethod("Serialize",
+ System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static));
+ Assert.IsNotNull(serializerType.GetMethod("Deserialize",
+ System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static));
+ }
+}
diff --git a/AyCode.Core.Tests/Serialization/QuickBenchmark.cs b/AyCode.Core.Tests/Serialization/QuickBenchmark.cs
index 9aaf3fc..27031a5 100644
--- a/AyCode.Core.Tests/Serialization/QuickBenchmark.cs
+++ b/AyCode.Core.Tests/Serialization/QuickBenchmark.cs
@@ -1,5 +1,6 @@
using System.Diagnostics;
using AyCode.Core.Extensions;
+using AyCode.Core.Serializers.Attributes;
using AyCode.Core.Serializers.Binaries;
using AyCode.Core.Serializers.Jsons;
using AyCode.Core.Tests.TestModels;
@@ -666,6 +667,7 @@ public class QuickBenchmark
#region Test Models
+ [AcBinarySerializable]
public class TestClassWithRepeatedValues
{
public int Id { get; set; }
diff --git a/AyCode.Core.Tests/TestModels/GeneratedSerializerTestModels.cs b/AyCode.Core.Tests/TestModels/GeneratedSerializerTestModels.cs
new file mode 100644
index 0000000..3b6b5ec
--- /dev/null
+++ b/AyCode.Core.Tests/TestModels/GeneratedSerializerTestModels.cs
@@ -0,0 +1,31 @@
+using AyCode.Core.Serializers.Attributes;
+
+namespace AyCode.Core.Tests.TestModels;
+
+///
+/// Test model for Source Generator based binary serialization.
+/// This class will have generated Serialize/Deserialize methods.
+///
+[AcBinarySerializable]
+public class GeneratedSerializerTestModel
+{
+ public int Id { get; set; }
+ public string? Name { get; set; }
+ public bool IsActive { get; set; }
+ public double Value { get; set; }
+ public DateTime Created { get; set; }
+ public Guid UniqueId { get; set; }
+ public decimal Price { get; set; }
+ public long BigNumber { get; set; }
+}
+
+///
+/// Simple test model with only primitives.
+///
+[AcBinarySerializable]
+public class SimpleGeneratedModel
+{
+ public int Age { get; set; }
+ public string? FirstName { get; set; }
+ public string? LastName { get; set; }
+}
diff --git a/AyCode.Core.Tests/TestModels/SharedTestModels.cs b/AyCode.Core.Tests/TestModels/SharedTestModels.cs
index 60949a1..e2c7077 100644
--- a/AyCode.Core.Tests/TestModels/SharedTestModels.cs
+++ b/AyCode.Core.Tests/TestModels/SharedTestModels.cs
@@ -1,5 +1,6 @@
using AyCode.Core.Extensions;
using AyCode.Core.Interfaces;
+using AyCode.Core.Serializers.Attributes;
using AyCode.Core.Serializers.Jsons;
using MessagePack;
using MongoDB.Bson.Serialization.Attributes;
@@ -51,6 +52,7 @@ public enum TestUserRole
/// Shared tag/label - used across multiple entities for cross-reference testing.
/// Implements IId<int> for semantic $id/$ref serialization.
///
+[AcBinarySerializable]
public class SharedTag : IId
{
public int Id { get; set; }
@@ -65,6 +67,7 @@ public class SharedTag : IId
///
/// Shared category - for hierarchical cross-reference testing.
///
+[AcBinarySerializable]
public class SharedCategory : IId
{
public int Id { get; set; }
@@ -80,6 +83,7 @@ public class SharedCategory : IId
///
/// Shared user reference - appears in many places to test $ref deduplication.
///
+[AcBinarySerializable]
public class SharedUser : IId
{
public int Id { get; set; }
@@ -97,6 +101,7 @@ public class SharedUser : IId
///
/// User preferences - non-IId nested object
///
+[AcBinarySerializable]
public class UserPreferences
{
public string Theme { get; set; } = "light";
@@ -113,6 +118,7 @@ public class UserPreferences
/// Non-IId metadata class - uses Newtonsoft PreserveReferencesHandling (numeric $id/$ref).
/// Does NOT implement IId, so uses standard Newtonsoft reference tracking.
///
+[AcBinarySerializable]
public class MetadataInfo
{
public string Key { get; set; } = "";
@@ -132,6 +138,7 @@ public class MetadataInfo
///
/// Level 1: Main order - root of the hierarchy
///
+[AcBinarySerializable]
public class TestOrder : IId
{
public int Id { get; set; }
@@ -172,6 +179,7 @@ public class TestOrder : IId
///
/// Level 2: Order item with pallets
///
+[AcBinarySerializable]
public class TestOrderItem : IId
{
public int Id { get; set; }
@@ -198,6 +206,7 @@ public class TestOrderItem : IId
///
/// Level 3: Pallet containing measurements
///
+[AcBinarySerializable]
public class TestPallet : IId
{
public int Id { get; set; }
@@ -222,6 +231,7 @@ public class TestPallet : IId
///
/// Level 4: Measurement with multiple points
///
+[AcBinarySerializable]
public class TestMeasurement : IId
{
public int Id { get; set; }
@@ -242,6 +252,7 @@ public class TestMeasurement : IId
///
/// Level 5: Deepest level - measurement point
///
+[AcBinarySerializable]
public class TestMeasurementPoint : IId
{
public int Id { get; set; }
@@ -263,6 +274,7 @@ public class TestMeasurementPoint : IId
///
/// Order with Guid Id - for testing Guid-based IId
///
+[AcBinarySerializable]
public class TestGuidOrder : IId
{
public Guid Id { get; set; }
@@ -274,6 +286,7 @@ public class TestGuidOrder : IId
///
/// Item with Guid Id
///
+[AcBinarySerializable]
public class TestGuidItem : IId
{
public Guid Id { get; set; }
@@ -289,6 +302,7 @@ public class TestGuidItem : IId
/// Simulates NopCommerce GenericAttribute - stores key-value pairs where DateTime values
/// are stored as strings in the database.
///
+[AcBinarySerializable]
public class TestGenericAttribute
{
public int Id { get; set; }
@@ -300,6 +314,7 @@ public class TestGenericAttribute
/// DTO with GenericAttributes collection - simulates OrderDto with string-stored DateTime values.
/// This reproduces the production bug where Binary serialization was thought to corrupt DateTime strings.
///
+[AcBinarySerializable]
public class TestDtoWithGenericAttributes : IId
{
public int Id { get; set; }
@@ -310,6 +325,7 @@ public class TestDtoWithGenericAttributes : IId
///
/// Order with nullable collections for null vs empty testing
///
+[AcBinarySerializable]
public class TestOrderWithNullableCollections
{
public int Id { get; set; }
@@ -321,6 +337,7 @@ public class TestOrderWithNullableCollections
///
/// Class with all primitive types for WASM/serialization testing
///
+[AcBinarySerializable]
public class PrimitiveTestClass
{
public int IntValue { get; set; }
@@ -343,6 +360,7 @@ public class PrimitiveTestClass
/// Class with extended primitive types for full serializer coverage.
/// Includes DateTimeOffset, TimeSpan, Dictionary, null properties.
///
+[AcBinarySerializable]
public class ExtendedPrimitiveTestClass
{
public int Id { get; set; }
@@ -372,6 +390,7 @@ public class ExtendedPrimitiveTestClass
///
/// Class with array of objects containing null items for WriteNull coverage
///
+[AcBinarySerializable]
public class ObjectWithNullItems
{
public int Id { get; set; }
@@ -386,6 +405,7 @@ public class ObjectWithNullItems
/// "Server-side" DTO with extra properties that the "client" doesn't know about.
/// Used to test SkipValue functionality when deserializing unknown properties.
///
+[AcBinarySerializable]
public class ServerCustomerDto : IId
{
public int Id { get; set; }
@@ -418,6 +438,7 @@ public class ServerCustomerDto : IId
/// the deserializer must skip unknown properties correctly
/// while still maintaining string intern table consistency.
///
+[AcBinarySerializable]
public class ClientCustomerDto : IId
{
public int Id { get; set; }
@@ -431,6 +452,7 @@ public class ClientCustomerDto : IId
/// Server DTO with nested objects that client doesn't know about.
/// Tests skipping complex nested structures.
///
+[AcBinarySerializable]
public class ServerOrderWithExtras : IId
{
public int Id { get; set; }
@@ -451,6 +473,7 @@ public class ServerOrderWithExtras : IId
///
/// Client version of the order - doesn't have Customer/RelatedCustomers properties.
///
+[AcBinarySerializable]
public class ClientOrderSimple : IId
{
public int Id { get; set; }
diff --git a/AyCode.Core.sln b/AyCode.Core.sln
index 3caa24d..7e33393 100644
--- a/AyCode.Core.sln
+++ b/AyCode.Core.sln
@@ -50,122 +50,388 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AyCode.Benchmark", "AyCode.
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AyCode.Services.Tests", "AyCode.Services.Tests\AyCode.Services.Tests.csproj", "{B8443014-1247-FB9C-7BF4-2CC944075A8B}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AyCode.Core.Serializers.SourceGenerator", "AyCode.Core.Serializers.SourceGenerator\AyCode.Core.Serializers.SourceGenerator.csproj", "{4A817897-80A8-4F42-86C5-20447401E0AA}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
Product|Any CPU = Product|Any CPU
+ Product|x64 = Product|x64
+ Product|x86 = Product|x86
Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{8CCC4969-7306-4747-8A58-80AC5A062EE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8CCC4969-7306-4747-8A58-80AC5A062EE1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8CCC4969-7306-4747-8A58-80AC5A062EE1}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {8CCC4969-7306-4747-8A58-80AC5A062EE1}.Debug|x64.Build.0 = Debug|Any CPU
+ {8CCC4969-7306-4747-8A58-80AC5A062EE1}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {8CCC4969-7306-4747-8A58-80AC5A062EE1}.Debug|x86.Build.0 = Debug|Any CPU
{8CCC4969-7306-4747-8A58-80AC5A062EE1}.Product|Any CPU.ActiveCfg = Product|Any CPU
{8CCC4969-7306-4747-8A58-80AC5A062EE1}.Product|Any CPU.Build.0 = Product|Any CPU
+ {8CCC4969-7306-4747-8A58-80AC5A062EE1}.Product|x64.ActiveCfg = Product|Any CPU
+ {8CCC4969-7306-4747-8A58-80AC5A062EE1}.Product|x64.Build.0 = Product|Any CPU
+ {8CCC4969-7306-4747-8A58-80AC5A062EE1}.Product|x86.ActiveCfg = Product|Any CPU
+ {8CCC4969-7306-4747-8A58-80AC5A062EE1}.Product|x86.Build.0 = Product|Any CPU
{8CCC4969-7306-4747-8A58-80AC5A062EE1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8CCC4969-7306-4747-8A58-80AC5A062EE1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8CCC4969-7306-4747-8A58-80AC5A062EE1}.Release|x64.ActiveCfg = Release|Any CPU
+ {8CCC4969-7306-4747-8A58-80AC5A062EE1}.Release|x64.Build.0 = Release|Any CPU
+ {8CCC4969-7306-4747-8A58-80AC5A062EE1}.Release|x86.ActiveCfg = Release|Any CPU
+ {8CCC4969-7306-4747-8A58-80AC5A062EE1}.Release|x86.Build.0 = Release|Any CPU
{A2105535-1397-4307-B61B-E49C983353B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A2105535-1397-4307-B61B-E49C983353B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A2105535-1397-4307-B61B-E49C983353B9}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A2105535-1397-4307-B61B-E49C983353B9}.Debug|x64.Build.0 = Debug|Any CPU
+ {A2105535-1397-4307-B61B-E49C983353B9}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A2105535-1397-4307-B61B-E49C983353B9}.Debug|x86.Build.0 = Debug|Any CPU
{A2105535-1397-4307-B61B-E49C983353B9}.Product|Any CPU.ActiveCfg = Product|Any CPU
{A2105535-1397-4307-B61B-E49C983353B9}.Product|Any CPU.Build.0 = Product|Any CPU
+ {A2105535-1397-4307-B61B-E49C983353B9}.Product|x64.ActiveCfg = Product|Any CPU
+ {A2105535-1397-4307-B61B-E49C983353B9}.Product|x64.Build.0 = Product|Any CPU
+ {A2105535-1397-4307-B61B-E49C983353B9}.Product|x86.ActiveCfg = Product|Any CPU
+ {A2105535-1397-4307-B61B-E49C983353B9}.Product|x86.Build.0 = Product|Any CPU
{A2105535-1397-4307-B61B-E49C983353B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A2105535-1397-4307-B61B-E49C983353B9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A2105535-1397-4307-B61B-E49C983353B9}.Release|x64.ActiveCfg = Release|Any CPU
+ {A2105535-1397-4307-B61B-E49C983353B9}.Release|x64.Build.0 = Release|Any CPU
+ {A2105535-1397-4307-B61B-E49C983353B9}.Release|x86.ActiveCfg = Release|Any CPU
+ {A2105535-1397-4307-B61B-E49C983353B9}.Release|x86.Build.0 = Release|Any CPU
{FB027D80-8949-403B-9A86-8E99F305016E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FB027D80-8949-403B-9A86-8E99F305016E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FB027D80-8949-403B-9A86-8E99F305016E}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {FB027D80-8949-403B-9A86-8E99F305016E}.Debug|x64.Build.0 = Debug|Any CPU
+ {FB027D80-8949-403B-9A86-8E99F305016E}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {FB027D80-8949-403B-9A86-8E99F305016E}.Debug|x86.Build.0 = Debug|Any CPU
{FB027D80-8949-403B-9A86-8E99F305016E}.Product|Any CPU.ActiveCfg = Product|Any CPU
{FB027D80-8949-403B-9A86-8E99F305016E}.Product|Any CPU.Build.0 = Product|Any CPU
+ {FB027D80-8949-403B-9A86-8E99F305016E}.Product|x64.ActiveCfg = Product|Any CPU
+ {FB027D80-8949-403B-9A86-8E99F305016E}.Product|x64.Build.0 = Product|Any CPU
+ {FB027D80-8949-403B-9A86-8E99F305016E}.Product|x86.ActiveCfg = Product|Any CPU
+ {FB027D80-8949-403B-9A86-8E99F305016E}.Product|x86.Build.0 = Product|Any CPU
{FB027D80-8949-403B-9A86-8E99F305016E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FB027D80-8949-403B-9A86-8E99F305016E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FB027D80-8949-403B-9A86-8E99F305016E}.Release|x64.ActiveCfg = Release|Any CPU
+ {FB027D80-8949-403B-9A86-8E99F305016E}.Release|x64.Build.0 = Release|Any CPU
+ {FB027D80-8949-403B-9A86-8E99F305016E}.Release|x86.ActiveCfg = Release|Any CPU
+ {FB027D80-8949-403B-9A86-8E99F305016E}.Release|x86.Build.0 = Release|Any CPU
{CAB60420-9F66-42D9-B67E-8E837DBA1F30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CAB60420-9F66-42D9-B67E-8E837DBA1F30}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CAB60420-9F66-42D9-B67E-8E837DBA1F30}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {CAB60420-9F66-42D9-B67E-8E837DBA1F30}.Debug|x64.Build.0 = Debug|Any CPU
+ {CAB60420-9F66-42D9-B67E-8E837DBA1F30}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CAB60420-9F66-42D9-B67E-8E837DBA1F30}.Debug|x86.Build.0 = Debug|Any CPU
{CAB60420-9F66-42D9-B67E-8E837DBA1F30}.Product|Any CPU.ActiveCfg = Product|Any CPU
{CAB60420-9F66-42D9-B67E-8E837DBA1F30}.Product|Any CPU.Build.0 = Product|Any CPU
+ {CAB60420-9F66-42D9-B67E-8E837DBA1F30}.Product|x64.ActiveCfg = Product|Any CPU
+ {CAB60420-9F66-42D9-B67E-8E837DBA1F30}.Product|x64.Build.0 = Product|Any CPU
+ {CAB60420-9F66-42D9-B67E-8E837DBA1F30}.Product|x86.ActiveCfg = Product|Any CPU
+ {CAB60420-9F66-42D9-B67E-8E837DBA1F30}.Product|x86.Build.0 = Product|Any CPU
{CAB60420-9F66-42D9-B67E-8E837DBA1F30}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CAB60420-9F66-42D9-B67E-8E837DBA1F30}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CAB60420-9F66-42D9-B67E-8E837DBA1F30}.Release|x64.ActiveCfg = Release|Any CPU
+ {CAB60420-9F66-42D9-B67E-8E837DBA1F30}.Release|x64.Build.0 = Release|Any CPU
+ {CAB60420-9F66-42D9-B67E-8E837DBA1F30}.Release|x86.ActiveCfg = Release|Any CPU
+ {CAB60420-9F66-42D9-B67E-8E837DBA1F30}.Release|x86.Build.0 = Release|Any CPU
{DC42F79D-EEF0-4F32-8608-230F24C6F22A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DC42F79D-EEF0-4F32-8608-230F24C6F22A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DC42F79D-EEF0-4F32-8608-230F24C6F22A}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {DC42F79D-EEF0-4F32-8608-230F24C6F22A}.Debug|x64.Build.0 = Debug|Any CPU
+ {DC42F79D-EEF0-4F32-8608-230F24C6F22A}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {DC42F79D-EEF0-4F32-8608-230F24C6F22A}.Debug|x86.Build.0 = Debug|Any CPU
{DC42F79D-EEF0-4F32-8608-230F24C6F22A}.Product|Any CPU.ActiveCfg = Product|Any CPU
{DC42F79D-EEF0-4F32-8608-230F24C6F22A}.Product|Any CPU.Build.0 = Product|Any CPU
+ {DC42F79D-EEF0-4F32-8608-230F24C6F22A}.Product|x64.ActiveCfg = Product|Any CPU
+ {DC42F79D-EEF0-4F32-8608-230F24C6F22A}.Product|x64.Build.0 = Product|Any CPU
+ {DC42F79D-EEF0-4F32-8608-230F24C6F22A}.Product|x86.ActiveCfg = Product|Any CPU
+ {DC42F79D-EEF0-4F32-8608-230F24C6F22A}.Product|x86.Build.0 = Product|Any CPU
{DC42F79D-EEF0-4F32-8608-230F24C6F22A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DC42F79D-EEF0-4F32-8608-230F24C6F22A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DC42F79D-EEF0-4F32-8608-230F24C6F22A}.Release|x64.ActiveCfg = Release|Any CPU
+ {DC42F79D-EEF0-4F32-8608-230F24C6F22A}.Release|x64.Build.0 = Release|Any CPU
+ {DC42F79D-EEF0-4F32-8608-230F24C6F22A}.Release|x86.ActiveCfg = Release|Any CPU
+ {DC42F79D-EEF0-4F32-8608-230F24C6F22A}.Release|x86.Build.0 = Release|Any CPU
{0B5AC35E-3E71-42DC-B503-80D6D3089F91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0B5AC35E-3E71-42DC-B503-80D6D3089F91}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0B5AC35E-3E71-42DC-B503-80D6D3089F91}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {0B5AC35E-3E71-42DC-B503-80D6D3089F91}.Debug|x64.Build.0 = Debug|Any CPU
+ {0B5AC35E-3E71-42DC-B503-80D6D3089F91}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {0B5AC35E-3E71-42DC-B503-80D6D3089F91}.Debug|x86.Build.0 = Debug|Any CPU
{0B5AC35E-3E71-42DC-B503-80D6D3089F91}.Product|Any CPU.ActiveCfg = Product|Any CPU
{0B5AC35E-3E71-42DC-B503-80D6D3089F91}.Product|Any CPU.Build.0 = Product|Any CPU
+ {0B5AC35E-3E71-42DC-B503-80D6D3089F91}.Product|x64.ActiveCfg = Product|Any CPU
+ {0B5AC35E-3E71-42DC-B503-80D6D3089F91}.Product|x64.Build.0 = Product|Any CPU
+ {0B5AC35E-3E71-42DC-B503-80D6D3089F91}.Product|x86.ActiveCfg = Product|Any CPU
+ {0B5AC35E-3E71-42DC-B503-80D6D3089F91}.Product|x86.Build.0 = Product|Any CPU
{0B5AC35E-3E71-42DC-B503-80D6D3089F91}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0B5AC35E-3E71-42DC-B503-80D6D3089F91}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0B5AC35E-3E71-42DC-B503-80D6D3089F91}.Release|x64.ActiveCfg = Release|Any CPU
+ {0B5AC35E-3E71-42DC-B503-80D6D3089F91}.Release|x64.Build.0 = Release|Any CPU
+ {0B5AC35E-3E71-42DC-B503-80D6D3089F91}.Release|x86.ActiveCfg = Release|Any CPU
+ {0B5AC35E-3E71-42DC-B503-80D6D3089F91}.Release|x86.Build.0 = Release|Any CPU
{F8ECCA33-B5EA-490D-B1A1-D33B5E4238A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F8ECCA33-B5EA-490D-B1A1-D33B5E4238A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F8ECCA33-B5EA-490D-B1A1-D33B5E4238A5}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F8ECCA33-B5EA-490D-B1A1-D33B5E4238A5}.Debug|x64.Build.0 = Debug|Any CPU
+ {F8ECCA33-B5EA-490D-B1A1-D33B5E4238A5}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F8ECCA33-B5EA-490D-B1A1-D33B5E4238A5}.Debug|x86.Build.0 = Debug|Any CPU
{F8ECCA33-B5EA-490D-B1A1-D33B5E4238A5}.Product|Any CPU.ActiveCfg = Product|Any CPU
{F8ECCA33-B5EA-490D-B1A1-D33B5E4238A5}.Product|Any CPU.Build.0 = Product|Any CPU
+ {F8ECCA33-B5EA-490D-B1A1-D33B5E4238A5}.Product|x64.ActiveCfg = Product|Any CPU
+ {F8ECCA33-B5EA-490D-B1A1-D33B5E4238A5}.Product|x64.Build.0 = Product|Any CPU
+ {F8ECCA33-B5EA-490D-B1A1-D33B5E4238A5}.Product|x86.ActiveCfg = Product|Any CPU
+ {F8ECCA33-B5EA-490D-B1A1-D33B5E4238A5}.Product|x86.Build.0 = Product|Any CPU
{F8ECCA33-B5EA-490D-B1A1-D33B5E4238A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F8ECCA33-B5EA-490D-B1A1-D33B5E4238A5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F8ECCA33-B5EA-490D-B1A1-D33B5E4238A5}.Release|x64.ActiveCfg = Release|Any CPU
+ {F8ECCA33-B5EA-490D-B1A1-D33B5E4238A5}.Release|x64.Build.0 = Release|Any CPU
+ {F8ECCA33-B5EA-490D-B1A1-D33B5E4238A5}.Release|x86.ActiveCfg = Release|Any CPU
+ {F8ECCA33-B5EA-490D-B1A1-D33B5E4238A5}.Release|x86.Build.0 = Release|Any CPU
{35D47907-CE4F-435B-823E-A02BE59C16D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{35D47907-CE4F-435B-823E-A02BE59C16D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {35D47907-CE4F-435B-823E-A02BE59C16D7}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {35D47907-CE4F-435B-823E-A02BE59C16D7}.Debug|x64.Build.0 = Debug|Any CPU
+ {35D47907-CE4F-435B-823E-A02BE59C16D7}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {35D47907-CE4F-435B-823E-A02BE59C16D7}.Debug|x86.Build.0 = Debug|Any CPU
{35D47907-CE4F-435B-823E-A02BE59C16D7}.Product|Any CPU.ActiveCfg = Product|Any CPU
{35D47907-CE4F-435B-823E-A02BE59C16D7}.Product|Any CPU.Build.0 = Product|Any CPU
+ {35D47907-CE4F-435B-823E-A02BE59C16D7}.Product|x64.ActiveCfg = Product|Any CPU
+ {35D47907-CE4F-435B-823E-A02BE59C16D7}.Product|x64.Build.0 = Product|Any CPU
+ {35D47907-CE4F-435B-823E-A02BE59C16D7}.Product|x86.ActiveCfg = Product|Any CPU
+ {35D47907-CE4F-435B-823E-A02BE59C16D7}.Product|x86.Build.0 = Product|Any CPU
{35D47907-CE4F-435B-823E-A02BE59C16D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{35D47907-CE4F-435B-823E-A02BE59C16D7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {35D47907-CE4F-435B-823E-A02BE59C16D7}.Release|x64.ActiveCfg = Release|Any CPU
+ {35D47907-CE4F-435B-823E-A02BE59C16D7}.Release|x64.Build.0 = Release|Any CPU
+ {35D47907-CE4F-435B-823E-A02BE59C16D7}.Release|x86.ActiveCfg = Release|Any CPU
+ {35D47907-CE4F-435B-823E-A02BE59C16D7}.Release|x86.Build.0 = Release|Any CPU
{EBC6371C-9454-473D-9547-DF9DECEB2D2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EBC6371C-9454-473D-9547-DF9DECEB2D2A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EBC6371C-9454-473D-9547-DF9DECEB2D2A}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {EBC6371C-9454-473D-9547-DF9DECEB2D2A}.Debug|x64.Build.0 = Debug|Any CPU
+ {EBC6371C-9454-473D-9547-DF9DECEB2D2A}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {EBC6371C-9454-473D-9547-DF9DECEB2D2A}.Debug|x86.Build.0 = Debug|Any CPU
{EBC6371C-9454-473D-9547-DF9DECEB2D2A}.Product|Any CPU.ActiveCfg = Product|Any CPU
{EBC6371C-9454-473D-9547-DF9DECEB2D2A}.Product|Any CPU.Build.0 = Product|Any CPU
+ {EBC6371C-9454-473D-9547-DF9DECEB2D2A}.Product|x64.ActiveCfg = Product|Any CPU
+ {EBC6371C-9454-473D-9547-DF9DECEB2D2A}.Product|x64.Build.0 = Product|Any CPU
+ {EBC6371C-9454-473D-9547-DF9DECEB2D2A}.Product|x86.ActiveCfg = Product|Any CPU
+ {EBC6371C-9454-473D-9547-DF9DECEB2D2A}.Product|x86.Build.0 = Product|Any CPU
{EBC6371C-9454-473D-9547-DF9DECEB2D2A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EBC6371C-9454-473D-9547-DF9DECEB2D2A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EBC6371C-9454-473D-9547-DF9DECEB2D2A}.Release|x64.ActiveCfg = Release|Any CPU
+ {EBC6371C-9454-473D-9547-DF9DECEB2D2A}.Release|x64.Build.0 = Release|Any CPU
+ {EBC6371C-9454-473D-9547-DF9DECEB2D2A}.Release|x86.ActiveCfg = Release|Any CPU
+ {EBC6371C-9454-473D-9547-DF9DECEB2D2A}.Release|x86.Build.0 = Release|Any CPU
{15272F57-771E-47BE-A960-AD75935254D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{15272F57-771E-47BE-A960-AD75935254D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {15272F57-771E-47BE-A960-AD75935254D0}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {15272F57-771E-47BE-A960-AD75935254D0}.Debug|x64.Build.0 = Debug|Any CPU
+ {15272F57-771E-47BE-A960-AD75935254D0}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {15272F57-771E-47BE-A960-AD75935254D0}.Debug|x86.Build.0 = Debug|Any CPU
{15272F57-771E-47BE-A960-AD75935254D0}.Product|Any CPU.ActiveCfg = Product|Any CPU
+ {15272F57-771E-47BE-A960-AD75935254D0}.Product|x64.ActiveCfg = Product|Any CPU
+ {15272F57-771E-47BE-A960-AD75935254D0}.Product|x64.Build.0 = Product|Any CPU
+ {15272F57-771E-47BE-A960-AD75935254D0}.Product|x86.ActiveCfg = Product|Any CPU
+ {15272F57-771E-47BE-A960-AD75935254D0}.Product|x86.Build.0 = Product|Any CPU
{15272F57-771E-47BE-A960-AD75935254D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {15272F57-771E-47BE-A960-AD75935254D0}.Release|x64.ActiveCfg = Release|Any CPU
+ {15272F57-771E-47BE-A960-AD75935254D0}.Release|x64.Build.0 = Release|Any CPU
+ {15272F57-771E-47BE-A960-AD75935254D0}.Release|x86.ActiveCfg = Release|Any CPU
+ {15272F57-771E-47BE-A960-AD75935254D0}.Release|x86.Build.0 = Release|Any CPU
{320A245F-6731-476D-A9D8-77888E6B5D9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{320A245F-6731-476D-A9D8-77888E6B5D9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {320A245F-6731-476D-A9D8-77888E6B5D9C}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {320A245F-6731-476D-A9D8-77888E6B5D9C}.Debug|x64.Build.0 = Debug|Any CPU
+ {320A245F-6731-476D-A9D8-77888E6B5D9C}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {320A245F-6731-476D-A9D8-77888E6B5D9C}.Debug|x86.Build.0 = Debug|Any CPU
{320A245F-6731-476D-A9D8-77888E6B5D9C}.Product|Any CPU.ActiveCfg = Product|Any CPU
+ {320A245F-6731-476D-A9D8-77888E6B5D9C}.Product|x64.ActiveCfg = Product|Any CPU
+ {320A245F-6731-476D-A9D8-77888E6B5D9C}.Product|x64.Build.0 = Product|Any CPU
+ {320A245F-6731-476D-A9D8-77888E6B5D9C}.Product|x86.ActiveCfg = Product|Any CPU
+ {320A245F-6731-476D-A9D8-77888E6B5D9C}.Product|x86.Build.0 = Product|Any CPU
{320A245F-6731-476D-A9D8-77888E6B5D9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{320A245F-6731-476D-A9D8-77888E6B5D9C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {320A245F-6731-476D-A9D8-77888E6B5D9C}.Release|x64.ActiveCfg = Release|Any CPU
+ {320A245F-6731-476D-A9D8-77888E6B5D9C}.Release|x64.Build.0 = Release|Any CPU
+ {320A245F-6731-476D-A9D8-77888E6B5D9C}.Release|x86.ActiveCfg = Release|Any CPU
+ {320A245F-6731-476D-A9D8-77888E6B5D9C}.Release|x86.Build.0 = Release|Any CPU
{21392620-7D0E-44B6-9485-93C57F944C20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{21392620-7D0E-44B6-9485-93C57F944C20}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {21392620-7D0E-44B6-9485-93C57F944C20}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {21392620-7D0E-44B6-9485-93C57F944C20}.Debug|x64.Build.0 = Debug|Any CPU
+ {21392620-7D0E-44B6-9485-93C57F944C20}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {21392620-7D0E-44B6-9485-93C57F944C20}.Debug|x86.Build.0 = Debug|Any CPU
{21392620-7D0E-44B6-9485-93C57F944C20}.Product|Any CPU.ActiveCfg = Product|Any CPU
{21392620-7D0E-44B6-9485-93C57F944C20}.Product|Any CPU.Build.0 = Product|Any CPU
+ {21392620-7D0E-44B6-9485-93C57F944C20}.Product|x64.ActiveCfg = Product|Any CPU
+ {21392620-7D0E-44B6-9485-93C57F944C20}.Product|x64.Build.0 = Product|Any CPU
+ {21392620-7D0E-44B6-9485-93C57F944C20}.Product|x86.ActiveCfg = Product|Any CPU
+ {21392620-7D0E-44B6-9485-93C57F944C20}.Product|x86.Build.0 = Product|Any CPU
{21392620-7D0E-44B6-9485-93C57F944C20}.Release|Any CPU.ActiveCfg = Release|Any CPU
{21392620-7D0E-44B6-9485-93C57F944C20}.Release|Any CPU.Build.0 = Release|Any CPU
+ {21392620-7D0E-44B6-9485-93C57F944C20}.Release|x64.ActiveCfg = Release|Any CPU
+ {21392620-7D0E-44B6-9485-93C57F944C20}.Release|x64.Build.0 = Release|Any CPU
+ {21392620-7D0E-44B6-9485-93C57F944C20}.Release|x86.ActiveCfg = Release|Any CPU
+ {21392620-7D0E-44B6-9485-93C57F944C20}.Release|x86.Build.0 = Release|Any CPU
{58C8A6A7-D624-4E32-93B9-16B879405CAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{58C8A6A7-D624-4E32-93B9-16B879405CAA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {58C8A6A7-D624-4E32-93B9-16B879405CAA}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {58C8A6A7-D624-4E32-93B9-16B879405CAA}.Debug|x64.Build.0 = Debug|Any CPU
+ {58C8A6A7-D624-4E32-93B9-16B879405CAA}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {58C8A6A7-D624-4E32-93B9-16B879405CAA}.Debug|x86.Build.0 = Debug|Any CPU
{58C8A6A7-D624-4E32-93B9-16B879405CAA}.Product|Any CPU.ActiveCfg = Product|Any CPU
{58C8A6A7-D624-4E32-93B9-16B879405CAA}.Product|Any CPU.Build.0 = Product|Any CPU
+ {58C8A6A7-D624-4E32-93B9-16B879405CAA}.Product|x64.ActiveCfg = Product|Any CPU
+ {58C8A6A7-D624-4E32-93B9-16B879405CAA}.Product|x64.Build.0 = Product|Any CPU
+ {58C8A6A7-D624-4E32-93B9-16B879405CAA}.Product|x86.ActiveCfg = Product|Any CPU
+ {58C8A6A7-D624-4E32-93B9-16B879405CAA}.Product|x86.Build.0 = Product|Any CPU
{58C8A6A7-D624-4E32-93B9-16B879405CAA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{58C8A6A7-D624-4E32-93B9-16B879405CAA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {58C8A6A7-D624-4E32-93B9-16B879405CAA}.Release|x64.ActiveCfg = Release|Any CPU
+ {58C8A6A7-D624-4E32-93B9-16B879405CAA}.Release|x64.Build.0 = Release|Any CPU
+ {58C8A6A7-D624-4E32-93B9-16B879405CAA}.Release|x86.ActiveCfg = Release|Any CPU
+ {58C8A6A7-D624-4E32-93B9-16B879405CAA}.Release|x86.Build.0 = Release|Any CPU
{44CF90C8-76E4-4BD6-A957-E8F7AE019B06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{44CF90C8-76E4-4BD6-A957-E8F7AE019B06}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {44CF90C8-76E4-4BD6-A957-E8F7AE019B06}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {44CF90C8-76E4-4BD6-A957-E8F7AE019B06}.Debug|x64.Build.0 = Debug|Any CPU
+ {44CF90C8-76E4-4BD6-A957-E8F7AE019B06}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {44CF90C8-76E4-4BD6-A957-E8F7AE019B06}.Debug|x86.Build.0 = Debug|Any CPU
{44CF90C8-76E4-4BD6-A957-E8F7AE019B06}.Product|Any CPU.ActiveCfg = Product|Any CPU
{44CF90C8-76E4-4BD6-A957-E8F7AE019B06}.Product|Any CPU.Build.0 = Product|Any CPU
+ {44CF90C8-76E4-4BD6-A957-E8F7AE019B06}.Product|x64.ActiveCfg = Product|Any CPU
+ {44CF90C8-76E4-4BD6-A957-E8F7AE019B06}.Product|x64.Build.0 = Product|Any CPU
+ {44CF90C8-76E4-4BD6-A957-E8F7AE019B06}.Product|x86.ActiveCfg = Product|Any CPU
+ {44CF90C8-76E4-4BD6-A957-E8F7AE019B06}.Product|x86.Build.0 = Product|Any CPU
{44CF90C8-76E4-4BD6-A957-E8F7AE019B06}.Release|Any CPU.ActiveCfg = Release|Any CPU
{44CF90C8-76E4-4BD6-A957-E8F7AE019B06}.Release|Any CPU.Build.0 = Release|Any CPU
+ {44CF90C8-76E4-4BD6-A957-E8F7AE019B06}.Release|x64.ActiveCfg = Release|Any CPU
+ {44CF90C8-76E4-4BD6-A957-E8F7AE019B06}.Release|x64.Build.0 = Release|Any CPU
+ {44CF90C8-76E4-4BD6-A957-E8F7AE019B06}.Release|x86.ActiveCfg = Release|Any CPU
+ {44CF90C8-76E4-4BD6-A957-E8F7AE019B06}.Release|x86.Build.0 = Release|Any CPU
{3C74C94F-2FEB-47F7-ABB3-B0C9CBCCC876}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3C74C94F-2FEB-47F7-ABB3-B0C9CBCCC876}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3C74C94F-2FEB-47F7-ABB3-B0C9CBCCC876}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {3C74C94F-2FEB-47F7-ABB3-B0C9CBCCC876}.Debug|x64.Build.0 = Debug|Any CPU
+ {3C74C94F-2FEB-47F7-ABB3-B0C9CBCCC876}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {3C74C94F-2FEB-47F7-ABB3-B0C9CBCCC876}.Debug|x86.Build.0 = Debug|Any CPU
{3C74C94F-2FEB-47F7-ABB3-B0C9CBCCC876}.Product|Any CPU.ActiveCfg = Product|Any CPU
{3C74C94F-2FEB-47F7-ABB3-B0C9CBCCC876}.Product|Any CPU.Build.0 = Product|Any CPU
+ {3C74C94F-2FEB-47F7-ABB3-B0C9CBCCC876}.Product|x64.ActiveCfg = Product|Any CPU
+ {3C74C94F-2FEB-47F7-ABB3-B0C9CBCCC876}.Product|x64.Build.0 = Product|Any CPU
+ {3C74C94F-2FEB-47F7-ABB3-B0C9CBCCC876}.Product|x86.ActiveCfg = Product|Any CPU
+ {3C74C94F-2FEB-47F7-ABB3-B0C9CBCCC876}.Product|x86.Build.0 = Product|Any CPU
{3C74C94F-2FEB-47F7-ABB3-B0C9CBCCC876}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3C74C94F-2FEB-47F7-ABB3-B0C9CBCCC876}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3C74C94F-2FEB-47F7-ABB3-B0C9CBCCC876}.Release|x64.ActiveCfg = Release|Any CPU
+ {3C74C94F-2FEB-47F7-ABB3-B0C9CBCCC876}.Release|x64.Build.0 = Release|Any CPU
+ {3C74C94F-2FEB-47F7-ABB3-B0C9CBCCC876}.Release|x86.ActiveCfg = Release|Any CPU
+ {3C74C94F-2FEB-47F7-ABB3-B0C9CBCCC876}.Release|x86.Build.0 = Release|Any CPU
{9AC9AF60-280A-4871-A7FA-69AB4D0C858A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9AC9AF60-280A-4871-A7FA-69AB4D0C858A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9AC9AF60-280A-4871-A7FA-69AB4D0C858A}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {9AC9AF60-280A-4871-A7FA-69AB4D0C858A}.Debug|x64.Build.0 = Debug|Any CPU
+ {9AC9AF60-280A-4871-A7FA-69AB4D0C858A}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {9AC9AF60-280A-4871-A7FA-69AB4D0C858A}.Debug|x86.Build.0 = Debug|Any CPU
{9AC9AF60-280A-4871-A7FA-69AB4D0C858A}.Product|Any CPU.ActiveCfg = Product|Any CPU
+ {9AC9AF60-280A-4871-A7FA-69AB4D0C858A}.Product|x64.ActiveCfg = Product|Any CPU
+ {9AC9AF60-280A-4871-A7FA-69AB4D0C858A}.Product|x64.Build.0 = Product|Any CPU
+ {9AC9AF60-280A-4871-A7FA-69AB4D0C858A}.Product|x86.ActiveCfg = Product|Any CPU
+ {9AC9AF60-280A-4871-A7FA-69AB4D0C858A}.Product|x86.Build.0 = Product|Any CPU
{9AC9AF60-280A-4871-A7FA-69AB4D0C858A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9AC9AF60-280A-4871-A7FA-69AB4D0C858A}.Release|x64.ActiveCfg = Release|Any CPU
+ {9AC9AF60-280A-4871-A7FA-69AB4D0C858A}.Release|x64.Build.0 = Release|Any CPU
+ {9AC9AF60-280A-4871-A7FA-69AB4D0C858A}.Release|x86.ActiveCfg = Release|Any CPU
+ {9AC9AF60-280A-4871-A7FA-69AB4D0C858A}.Release|x86.Build.0 = Release|Any CPU
{DE2DD6A4-A906-4BA6-8AAA-2A0433DF523F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DE2DD6A4-A906-4BA6-8AAA-2A0433DF523F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DE2DD6A4-A906-4BA6-8AAA-2A0433DF523F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {DE2DD6A4-A906-4BA6-8AAA-2A0433DF523F}.Debug|x64.Build.0 = Debug|Any CPU
+ {DE2DD6A4-A906-4BA6-8AAA-2A0433DF523F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {DE2DD6A4-A906-4BA6-8AAA-2A0433DF523F}.Debug|x86.Build.0 = Debug|Any CPU
{DE2DD6A4-A906-4BA6-8AAA-2A0433DF523F}.Product|Any CPU.ActiveCfg = Product|Any CPU
+ {DE2DD6A4-A906-4BA6-8AAA-2A0433DF523F}.Product|x64.ActiveCfg = Product|Any CPU
+ {DE2DD6A4-A906-4BA6-8AAA-2A0433DF523F}.Product|x64.Build.0 = Product|Any CPU
+ {DE2DD6A4-A906-4BA6-8AAA-2A0433DF523F}.Product|x86.ActiveCfg = Product|Any CPU
+ {DE2DD6A4-A906-4BA6-8AAA-2A0433DF523F}.Product|x86.Build.0 = Product|Any CPU
{DE2DD6A4-A906-4BA6-8AAA-2A0433DF523F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DE2DD6A4-A906-4BA6-8AAA-2A0433DF523F}.Release|x64.ActiveCfg = Release|Any CPU
+ {DE2DD6A4-A906-4BA6-8AAA-2A0433DF523F}.Release|x64.Build.0 = Release|Any CPU
+ {DE2DD6A4-A906-4BA6-8AAA-2A0433DF523F}.Release|x86.ActiveCfg = Release|Any CPU
+ {DE2DD6A4-A906-4BA6-8AAA-2A0433DF523F}.Release|x86.Build.0 = Release|Any CPU
{73261A8C-FB41-4C4C-90D4-ED5EEC991413}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{73261A8C-FB41-4C4C-90D4-ED5EEC991413}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {73261A8C-FB41-4C4C-90D4-ED5EEC991413}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {73261A8C-FB41-4C4C-90D4-ED5EEC991413}.Debug|x64.Build.0 = Debug|Any CPU
+ {73261A8C-FB41-4C4C-90D4-ED5EEC991413}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {73261A8C-FB41-4C4C-90D4-ED5EEC991413}.Debug|x86.Build.0 = Debug|Any CPU
{73261A8C-FB41-4C4C-90D4-ED5EEC991413}.Product|Any CPU.ActiveCfg = Product|Any CPU
+ {73261A8C-FB41-4C4C-90D4-ED5EEC991413}.Product|x64.ActiveCfg = Product|Any CPU
+ {73261A8C-FB41-4C4C-90D4-ED5EEC991413}.Product|x64.Build.0 = Product|Any CPU
+ {73261A8C-FB41-4C4C-90D4-ED5EEC991413}.Product|x86.ActiveCfg = Product|Any CPU
+ {73261A8C-FB41-4C4C-90D4-ED5EEC991413}.Product|x86.Build.0 = Product|Any CPU
{73261A8C-FB41-4C4C-90D4-ED5EEC991413}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {73261A8C-FB41-4C4C-90D4-ED5EEC991413}.Release|x64.ActiveCfg = Release|Any CPU
+ {73261A8C-FB41-4C4C-90D4-ED5EEC991413}.Release|x64.Build.0 = Release|Any CPU
+ {73261A8C-FB41-4C4C-90D4-ED5EEC991413}.Release|x86.ActiveCfg = Release|Any CPU
+ {73261A8C-FB41-4C4C-90D4-ED5EEC991413}.Release|x86.Build.0 = Release|Any CPU
{A20861A9-411E-6150-BF5C-69E8196E5D22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A20861A9-411E-6150-BF5C-69E8196E5D22}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A20861A9-411E-6150-BF5C-69E8196E5D22}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A20861A9-411E-6150-BF5C-69E8196E5D22}.Debug|x64.Build.0 = Debug|Any CPU
+ {A20861A9-411E-6150-BF5C-69E8196E5D22}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A20861A9-411E-6150-BF5C-69E8196E5D22}.Debug|x86.Build.0 = Debug|Any CPU
{A20861A9-411E-6150-BF5C-69E8196E5D22}.Product|Any CPU.ActiveCfg = Release|Any CPU
{A20861A9-411E-6150-BF5C-69E8196E5D22}.Product|Any CPU.Build.0 = Release|Any CPU
+ {A20861A9-411E-6150-BF5C-69E8196E5D22}.Product|x64.ActiveCfg = Product|Any CPU
+ {A20861A9-411E-6150-BF5C-69E8196E5D22}.Product|x64.Build.0 = Product|Any CPU
+ {A20861A9-411E-6150-BF5C-69E8196E5D22}.Product|x86.ActiveCfg = Product|Any CPU
+ {A20861A9-411E-6150-BF5C-69E8196E5D22}.Product|x86.Build.0 = Product|Any CPU
{A20861A9-411E-6150-BF5C-69E8196E5D22}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A20861A9-411E-6150-BF5C-69E8196E5D22}.Release|x64.ActiveCfg = Release|Any CPU
+ {A20861A9-411E-6150-BF5C-69E8196E5D22}.Release|x64.Build.0 = Release|Any CPU
+ {A20861A9-411E-6150-BF5C-69E8196E5D22}.Release|x86.ActiveCfg = Release|Any CPU
+ {A20861A9-411E-6150-BF5C-69E8196E5D22}.Release|x86.Build.0 = Release|Any CPU
{B8443014-1247-FB9C-7BF4-2CC944075A8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B8443014-1247-FB9C-7BF4-2CC944075A8B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B8443014-1247-FB9C-7BF4-2CC944075A8B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {B8443014-1247-FB9C-7BF4-2CC944075A8B}.Debug|x64.Build.0 = Debug|Any CPU
+ {B8443014-1247-FB9C-7BF4-2CC944075A8B}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {B8443014-1247-FB9C-7BF4-2CC944075A8B}.Debug|x86.Build.0 = Debug|Any CPU
{B8443014-1247-FB9C-7BF4-2CC944075A8B}.Product|Any CPU.ActiveCfg = Product|Any CPU
{B8443014-1247-FB9C-7BF4-2CC944075A8B}.Product|Any CPU.Build.0 = Product|Any CPU
+ {B8443014-1247-FB9C-7BF4-2CC944075A8B}.Product|x64.ActiveCfg = Product|Any CPU
+ {B8443014-1247-FB9C-7BF4-2CC944075A8B}.Product|x64.Build.0 = Product|Any CPU
+ {B8443014-1247-FB9C-7BF4-2CC944075A8B}.Product|x86.ActiveCfg = Product|Any CPU
+ {B8443014-1247-FB9C-7BF4-2CC944075A8B}.Product|x86.Build.0 = Product|Any CPU
{B8443014-1247-FB9C-7BF4-2CC944075A8B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B8443014-1247-FB9C-7BF4-2CC944075A8B}.Release|x64.ActiveCfg = Release|Any CPU
+ {B8443014-1247-FB9C-7BF4-2CC944075A8B}.Release|x64.Build.0 = Release|Any CPU
+ {B8443014-1247-FB9C-7BF4-2CC944075A8B}.Release|x86.ActiveCfg = Release|Any CPU
+ {B8443014-1247-FB9C-7BF4-2CC944075A8B}.Release|x86.Build.0 = Release|Any CPU
+ {4A817897-80A8-4F42-86C5-20447401E0AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4A817897-80A8-4F42-86C5-20447401E0AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4A817897-80A8-4F42-86C5-20447401E0AA}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {4A817897-80A8-4F42-86C5-20447401E0AA}.Debug|x64.Build.0 = Debug|Any CPU
+ {4A817897-80A8-4F42-86C5-20447401E0AA}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {4A817897-80A8-4F42-86C5-20447401E0AA}.Debug|x86.Build.0 = Debug|Any CPU
+ {4A817897-80A8-4F42-86C5-20447401E0AA}.Product|Any CPU.ActiveCfg = Debug|Any CPU
+ {4A817897-80A8-4F42-86C5-20447401E0AA}.Product|Any CPU.Build.0 = Debug|Any CPU
+ {4A817897-80A8-4F42-86C5-20447401E0AA}.Product|x64.ActiveCfg = Debug|Any CPU
+ {4A817897-80A8-4F42-86C5-20447401E0AA}.Product|x64.Build.0 = Debug|Any CPU
+ {4A817897-80A8-4F42-86C5-20447401E0AA}.Product|x86.ActiveCfg = Debug|Any CPU
+ {4A817897-80A8-4F42-86C5-20447401E0AA}.Product|x86.Build.0 = Debug|Any CPU
+ {4A817897-80A8-4F42-86C5-20447401E0AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4A817897-80A8-4F42-86C5-20447401E0AA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4A817897-80A8-4F42-86C5-20447401E0AA}.Release|x64.ActiveCfg = Release|Any CPU
+ {4A817897-80A8-4F42-86C5-20447401E0AA}.Release|x64.Build.0 = Release|Any CPU
+ {4A817897-80A8-4F42-86C5-20447401E0AA}.Release|x86.ActiveCfg = Release|Any CPU
+ {4A817897-80A8-4F42-86C5-20447401E0AA}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/AyCode.Core/AyCode.Core.csproj b/AyCode.Core/AyCode.Core.csproj
index 5a7c5eb..52a662e 100644
--- a/AyCode.Core/AyCode.Core.csproj
+++ b/AyCode.Core/AyCode.Core.csproj
@@ -7,6 +7,9 @@
+
diff --git a/AyCode.Core/Properties/AssemblyInfo.cs b/AyCode.Core/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..70e480e
--- /dev/null
+++ b/AyCode.Core/Properties/AssemblyInfo.cs
@@ -0,0 +1,6 @@
+using System.Runtime.CompilerServices;
+
+// Make internal types visible to test projects and generated code
+[assembly: InternalsVisibleTo("AyCode.Core.Tests")]
+[assembly: InternalsVisibleTo("AyCode.Core.Tests.Internal")]
+[assembly: InternalsVisibleTo("AyCode.Benchmark")]
diff --git a/AyCode.Core/Serializers/Attributes/AcBinarySerializableAttribute.cs b/AyCode.Core/Serializers/Attributes/AcBinarySerializableAttribute.cs
new file mode 100644
index 0000000..9acd1d1
--- /dev/null
+++ b/AyCode.Core/Serializers/Attributes/AcBinarySerializableAttribute.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace AyCode.Core.Serializers.Attributes;
+
+///
+/// Marks a class or struct for Source Generator based binary serialization.
+/// When applied, the Source Generator will create optimized serialize/deserialize methods
+/// at compile time, eliminating the need for runtime reflection or compiled expressions.
+///
+///
+/// If this attribute is not present, the serializer falls back to the existing
+/// compiled expression based approach which works for all types.
+///
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false, AllowMultiple = false)]
+public sealed class AcBinarySerializableAttribute : Attribute
+{
+}
diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.cs
index a2313e8..7d222e6 100644
--- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.cs
+++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.cs
@@ -9,6 +9,9 @@ namespace AyCode.Core.Serializers.Binaries;
public static partial class AcBinaryDeserializer
{
+ ///
+ /// Binary deserialization context. Public for generated serializers.
+ ///
internal ref struct BinaryDeserializationContext
{
private readonly ReadOnlySpan _buffer;
diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializeTypeMetadata.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializeTypeMetadata.cs
index f8f2723..cd923c4 100644
--- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializeTypeMetadata.cs
+++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializeTypeMetadata.cs
@@ -1,8 +1,7 @@
-using System.Collections;
-using System.Collections.Frozen;
using System.Reflection;
using System.Runtime.CompilerServices;
using AyCode.Core.Serializers;
+using AyCode.Core.Serializers.Attributes;
namespace AyCode.Core.Serializers.Binaries;
@@ -16,6 +15,19 @@ public static partial class AcBinaryDeserializer
///
public BinaryPropertySetterInfo[] PropertiesArray { get; }
+ ///
+ /// True if this type has a Source Generator generated deserializer available.
+ /// Note: Due to ref struct limitations, the generated code cannot be called via delegates.
+ /// The generated code must be called directly from consumer code that has access to it.
+ ///
+ public bool HasGeneratedDeserializer { get; }
+
+ ///
+ /// The generated serializer type, if available.
+ /// Consumer code can use this to call the generated methods directly.
+ ///
+ public Type? GeneratedSerializerType { get; }
+
public BinaryDeserializeTypeMetadata(Type type) : base(type)
{
var orderedProperties = GetSerializableProperties(type, requiresRead: true, requiresWrite: true);
@@ -25,6 +37,18 @@ public static partial class AcBinaryDeserializer
{
PropertiesArray[i] = new BinaryPropertySetterInfo(orderedProperties[i], type);
}
+
+ // Fast check: only look for generated serializer if type has [AcBinarySerializable] attribute
+ if (type.IsDefined(typeof(AcBinarySerializableAttribute), inherit: false))
+ {
+ GeneratedSerializerType = FindGeneratedSerializerType(type);
+ HasGeneratedDeserializer = GeneratedSerializerType != null;
+ }
+ else
+ {
+ GeneratedSerializerType = null;
+ HasGeneratedDeserializer = false;
+ }
}
///
@@ -33,6 +57,28 @@ public static partial class AcBinaryDeserializer
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public BinaryPropertySetterInfo? GetPropertyByIndex(int index)
=> (uint)index < (uint)PropertiesArray.Length ? PropertiesArray[index] : null;
+
+ ///
+ /// Finds the generated serializer type for the given type.
+ ///
+ private static Type? FindGeneratedSerializerType(Type type)
+ {
+ // Generated class name format: {ClassName}_AcBinarySerializer
+ var generatedTypeName = $"{type.FullName}_AcBinarySerializer";
+
+ // Try to find in the same assembly
+ var serializerType = type.Assembly.GetType(generatedTypeName);
+
+ // For nested types, the generated class is at namespace level
+ if (serializerType == null && type.IsNested)
+ {
+ var ns = type.Namespace ?? "";
+ var simpleName = $"{(string.IsNullOrEmpty(ns) ? "" : ns + ".")}{type.Name}_AcBinarySerializer";
+ serializerType = type.Assembly.GetType(simpleName);
+ }
+
+ return serializerType;
+ }
}
///
diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs
index eef28b1..134bdad 100644
--- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs
+++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs
@@ -44,6 +44,9 @@ public static partial class AcBinarySerializer
}
}
+ ///
+ /// Binary serialization context. Public for generated serializers.
+ ///
internal sealed class BinarySerializationContext : IDisposable
{
private const int MinBufferSize = 256;
diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinaryTypeMetadata.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinaryTypeMetadata.cs
index 8f80ce7..31999b6 100644
--- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinaryTypeMetadata.cs
+++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinaryTypeMetadata.cs
@@ -1,6 +1,8 @@
using System;
using System.Reflection;
+using System.Runtime.CompilerServices;
using AyCode.Core.Serializers;
+using AyCode.Core.Serializers.Attributes;
using static AyCode.Core.Helpers.JsonUtilities;
namespace AyCode.Core.Serializers.Binaries;
@@ -11,11 +13,18 @@ public static partial class AcBinarySerializer
{
public BinaryPropertyAccessor[] Properties { get; }
+ ///
+ /// True if this type has a Source Generator generated serializer available.
+ ///
+ public bool HasGeneratedSerializer { get; }
+
+ ///
+ /// The generated serializer type, if available.
+ ///
+ public Type? GeneratedSerializerType { get; }
+
public BinaryTypeMetadata(Type type) : base(type)
{
- // Use requiresWrite: true to match deserializer's property list
- // This ensures read-only properties (like computed properties) are excluded
- // and property indices are consistent between serialization and deserialization
var orderedProperties = GetSerializableProperties(type, requiresRead: true, requiresWrite: true);
Properties = new BinaryPropertyAccessor[orderedProperties.Length];
@@ -25,6 +34,29 @@ public static partial class AcBinarySerializer
accessor.PropertyIndex = i;
Properties[i] = accessor;
}
+
+ // Fast check: only look for generated serializer if type has [AcBinarySerializable] attribute
+ if (type.IsDefined(typeof(AcBinarySerializableAttribute), inherit: false))
+ {
+ GeneratedSerializerType = FindGeneratedSerializerType(type);
+ HasGeneratedSerializer = GeneratedSerializerType != null;
+ }
+ }
+
+ private static Type? FindGeneratedSerializerType(Type type)
+ {
+ var generatedTypeName = $"{type.FullName}_AcBinarySerializer";
+
+ var serializerType = type.Assembly.GetType(generatedTypeName);
+
+ if (serializerType == null && type.IsNested)
+ {
+ var ns = type.Namespace ?? "";
+ var simpleName = $"{(string.IsNullOrEmpty(ns) ? "" : ns + ".")}{type.Name}_AcBinarySerializer";
+ serializerType = type.Assembly.GetType(simpleName);
+ }
+
+ return serializerType;
}
}