Add AcBinary Source Generator for fast serialization

Introduce Roslyn Source Generator for AcBinary serialization, generating optimized Serialize/Deserialize methods for types marked with [AcBinarySerializable]. Integrate generator as analyzer in core, tests, and benchmarks. Update metadata to detect generated serializers. Add benchmarks and integration tests to validate performance and correctness. Update project files and internals visibility for testing. Existing runtime serialization remains as fallback.
This commit is contained in:
Loretta 2026-01-06 08:58:34 +01:00
parent ceb8c3d886
commit 05e91aab60
17 changed files with 1668 additions and 5 deletions

View File

@ -28,6 +28,10 @@
<ProjectReference Include="..\AyCode.Services\AyCode.Services.csproj" />
<ProjectReference Include="..\AyCode.Services.Server\AyCode.Services.Server.csproj" />
<ProjectReference Include="..\AyCode.Models.Server\AyCode.Models.Server.csproj" />
<!-- Source Generator for [AcBinarySerializable] marked types -->
<ProjectReference Include="..\AyCode.Core.Serializers.SourceGenerator\AyCode.Core.Serializers.SourceGenerator.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>
<ItemGroup>

View File

@ -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;
/// <summary>
/// Pure contractless model - NO MessagePack attributes.
/// This tests TRUE runtime serialization without any source generation.
/// </summary>
[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; } = "";
}
/// <summary>
/// Benchmark model with only primitive types - fully supported by Source Generator.
/// MessagePack attributes added for fair comparison with Source Generator.
/// </summary>
[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; } = "";
}
/// <summary>
/// TRUE Contractless benchmark - no attributes, pure runtime serialization.
/// </summary>
[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<PureContractlessModel>(_acBinaryData);
[Benchmark(Description = "MsgPack Contractless Deserialize")]
public PureContractlessModel? Deserialize_MsgPack_Contractless()
=> MessagePackSerializer.Deserialize<PureContractlessModel>(_msgPackContractlessData, _msgPackContractlessOptions);
}
/// <summary>
/// Benchmark comparing Source Generator vs Runtime serialization for primitive-only types.
/// Uses MessagePack with [MessagePackObject] attributes for fair Source Generator comparison.
/// </summary>
[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<PrimitiveBenchmarkModel>(_runtimeSerializedData);
[Benchmark(Description = "MsgPack SourceGen Deserialize")]
public PrimitiveBenchmarkModel? Deserialize_MsgPack_SourceGen()
=> MessagePackSerializer.Deserialize<PrimitiveBenchmarkModel>(_msgPackData, _msgPackOptions);
[Benchmark(Description = "MsgPack Contractless Deserialize")]
public PrimitiveBenchmarkModel? Deserialize_MsgPack_Contractless()
=> MessagePackSerializer.Deserialize<PrimitiveBenchmarkModel>(_msgPackContractlessData, _msgPackContractlessOptions);
#endregion
}
/// <summary>
/// Repeated string benchmark model - tests string interning performance.
/// MessagePack attributes added for fair comparison.
/// </summary>
[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; } = "";
}
/// <summary>
/// Benchmark for types with repeated string values - where AcBinary string interning helps.
/// Compares against both MessagePack SourceGen and Contractless modes.
/// </summary>
[ShortRunJob]
[MemoryDiagnoser]
[RankColumn]
public class RepeatedStringBenchmark
{
private List<RepeatedStringBenchmarkModel> _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<RepeatedStringBenchmarkModel>? Deserialize_AcBinary()
=> AcBinaryDeserializer.Deserialize<List<RepeatedStringBenchmarkModel>>(_acBinaryData);
[Benchmark(Description = "MsgPack SourceGen Deserialize")]
public List<RepeatedStringBenchmarkModel>? Deserialize_MsgPack_SourceGen()
=> MessagePackSerializer.Deserialize<List<RepeatedStringBenchmarkModel>>(_msgPackData, _msgPackOptions);
[Benchmark(Description = "MsgPack Contractless Deserialize")]
public List<RepeatedStringBenchmarkModel>? Deserialize_MsgPack_Contractless()
=> MessagePackSerializer.Deserialize<List<RepeatedStringBenchmarkModel>>(_msgPackContractlessData, _msgPackContractlessOptions);
}

View File

@ -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;
/// <summary>
/// Source Generator for AcBinary serialization.
/// Generates optimized serialize/deserialize methods for classes marked with [AcBinarySerializable].
/// </summary>
[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<PropertyInfo>();
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);
}
/// <summary>
/// Gets the nested type name chain (e.g., "OuterClass.MiddleClass.InnerClass")
/// </summary>
private static string GetNestedTypeName(INamedTypeSymbol typeSymbol)
{
var parts = new List<string>();
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<SerializableClassInfo> 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("// <auto-generated/>");
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}/// <summary>");
sb.AppendLine($"{indent}/// Generated binary serializer for {classInfo.ClassName}.");
sb.AppendLine($"{indent}/// </summary>");
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} /// <summary>");
sb.AppendLine($"{indent} /// Property names in serialization order (alphabetical).");
sb.AppendLine($"{indent} /// Used for runtime validation against TypeMetadataBase.GetSerializableProperties().");
sb.AppendLine($"{indent} /// </summary>");
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} /// <summary>");
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} /// </summary>");
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<T>) - 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);
}
/// <summary>
/// Checks if the type kind represents a nullable VALUE type (Nullable&lt;T&gt;), not a reference type
/// </summary>
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} /// <summary>");
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} /// </summary>");
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;
}
}
}
/// <summary>
/// Information about a class marked with [AcBinarySerializable].
/// </summary>
internal sealed class SerializableClassInfo
{
public string Namespace { get; }
public string ClassName { get; }
public string FullTypeName { get; }
public bool IsStruct { get; }
public bool IsNestedType { get; }
/// <summary>
/// The type name to use in method signatures. For nested types this includes the containing types.
/// e.g., "OuterClass.InnerClass"
/// </summary>
public string TypeNameForSignature { get; }
public List<PropertyInfo> Properties { get; }
public SerializableClassInfo(string ns, string className, string fullTypeName, bool isStruct, bool isNestedType, string typeNameForSignature, List<PropertyInfo> properties)
{
Namespace = ns;
ClassName = className;
FullTypeName = fullTypeName;
IsStruct = isStruct;
IsNestedType = isNestedType;
TypeNameForSignature = typeNameForSignature;
Properties = properties;
}
}
/// <summary>
/// Information about a serializable property.
/// </summary>
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;
}
}
/// <summary>
/// Kind of property type for code generation.
/// </summary>
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
}

View File

@ -3,6 +3,9 @@
<PropertyGroup>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<!-- Enable generated files output for debugging -->
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)Generated</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
<Import Project="..//AyCode.Core.targets" />
@ -25,6 +28,9 @@
<ItemGroup>
<ProjectReference Include="..\AyCode.Core.Server\AyCode.Core.Server.csproj" />
<ProjectReference Include="..\AyCode.Core\AyCode.Core.csproj" />
<ProjectReference Include="..\AyCode.Core.Serializers.SourceGenerator\AyCode.Core.Serializers.SourceGenerator.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
<ProjectReference Include="..\AyCode.Entities.Server\AyCode.Entities.Server.csproj" />
<ProjectReference Include="..\AyCode.Entities\AyCode.Entities.csproj" />
<ProjectReference Include="..\AyCode.Interfaces.Server\AyCode.Interfaces.Server.csproj" />

View File

@ -0,0 +1,149 @@
using AyCode.Core.Serializers.Binaries;
using AyCode.Core.Tests.TestModels;
namespace AyCode.Core.Tests.Serialization;
/// <summary>
/// Tests for Source Generator based serialization integration.
/// </summary>
[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<GeneratedSerializerTestModel>(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));
}
}

View File

@ -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; }

View File

@ -0,0 +1,31 @@
using AyCode.Core.Serializers.Attributes;
namespace AyCode.Core.Tests.TestModels;
/// <summary>
/// Test model for Source Generator based binary serialization.
/// This class will have generated Serialize/Deserialize methods.
/// </summary>
[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; }
}
/// <summary>
/// Simple test model with only primitives.
/// </summary>
[AcBinarySerializable]
public class SimpleGeneratedModel
{
public int Age { get; set; }
public string? FirstName { get; set; }
public string? LastName { get; set; }
}

View File

@ -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&lt;int&gt; for semantic $id/$ref serialization.
/// </summary>
[AcBinarySerializable]
public class SharedTag : IId<int>
{
public int Id { get; set; }
@ -65,6 +67,7 @@ public class SharedTag : IId<int>
/// <summary>
/// Shared category - for hierarchical cross-reference testing.
/// </summary>
[AcBinarySerializable]
public class SharedCategory : IId<int>
{
public int Id { get; set; }
@ -80,6 +83,7 @@ public class SharedCategory : IId<int>
/// <summary>
/// Shared user reference - appears in many places to test $ref deduplication.
/// </summary>
[AcBinarySerializable]
public class SharedUser : IId<int>
{
public int Id { get; set; }
@ -97,6 +101,7 @@ public class SharedUser : IId<int>
/// <summary>
/// User preferences - non-IId nested object
/// </summary>
[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.
/// </summary>
[AcBinarySerializable]
public class MetadataInfo
{
public string Key { get; set; } = "";
@ -132,6 +138,7 @@ public class MetadataInfo
/// <summary>
/// Level 1: Main order - root of the hierarchy
/// </summary>
[AcBinarySerializable]
public class TestOrder : IId<int>
{
public int Id { get; set; }
@ -172,6 +179,7 @@ public class TestOrder : IId<int>
/// <summary>
/// Level 2: Order item with pallets
/// </summary>
[AcBinarySerializable]
public class TestOrderItem : IId<int>
{
public int Id { get; set; }
@ -198,6 +206,7 @@ public class TestOrderItem : IId<int>
/// <summary>
/// Level 3: Pallet containing measurements
/// </summary>
[AcBinarySerializable]
public class TestPallet : IId<int>
{
public int Id { get; set; }
@ -222,6 +231,7 @@ public class TestPallet : IId<int>
/// <summary>
/// Level 4: Measurement with multiple points
/// </summary>
[AcBinarySerializable]
public class TestMeasurement : IId<int>
{
public int Id { get; set; }
@ -242,6 +252,7 @@ public class TestMeasurement : IId<int>
/// <summary>
/// Level 5: Deepest level - measurement point
/// </summary>
[AcBinarySerializable]
public class TestMeasurementPoint : IId<int>
{
public int Id { get; set; }
@ -263,6 +274,7 @@ public class TestMeasurementPoint : IId<int>
/// <summary>
/// Order with Guid Id - for testing Guid-based IId
/// </summary>
[AcBinarySerializable]
public class TestGuidOrder : IId<Guid>
{
public Guid Id { get; set; }
@ -274,6 +286,7 @@ public class TestGuidOrder : IId<Guid>
/// <summary>
/// Item with Guid Id
/// </summary>
[AcBinarySerializable]
public class TestGuidItem : IId<Guid>
{
public Guid Id { get; set; }
@ -289,6 +302,7 @@ public class TestGuidItem : IId<Guid>
/// Simulates NopCommerce GenericAttribute - stores key-value pairs where DateTime values
/// are stored as strings in the database.
/// </summary>
[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.
/// </summary>
[AcBinarySerializable]
public class TestDtoWithGenericAttributes : IId<int>
{
public int Id { get; set; }
@ -310,6 +325,7 @@ public class TestDtoWithGenericAttributes : IId<int>
/// <summary>
/// Order with nullable collections for null vs empty testing
/// </summary>
[AcBinarySerializable]
public class TestOrderWithNullableCollections
{
public int Id { get; set; }
@ -321,6 +337,7 @@ public class TestOrderWithNullableCollections
/// <summary>
/// Class with all primitive types for WASM/serialization testing
/// </summary>
[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.
/// </summary>
[AcBinarySerializable]
public class ExtendedPrimitiveTestClass
{
public int Id { get; set; }
@ -372,6 +390,7 @@ public class ExtendedPrimitiveTestClass
/// <summary>
/// Class with array of objects containing null items for WriteNull coverage
/// </summary>
[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.
/// </summary>
[AcBinarySerializable]
public class ServerCustomerDto : IId<int>
{
public int Id { get; set; }
@ -418,6 +438,7 @@ public class ServerCustomerDto : IId<int>
/// the deserializer must skip unknown properties correctly
/// while still maintaining string intern table consistency.
/// </summary>
[AcBinarySerializable]
public class ClientCustomerDto : IId<int>
{
public int Id { get; set; }
@ -431,6 +452,7 @@ public class ClientCustomerDto : IId<int>
/// Server DTO with nested objects that client doesn't know about.
/// Tests skipping complex nested structures.
/// </summary>
[AcBinarySerializable]
public class ServerOrderWithExtras : IId<int>
{
public int Id { get; set; }
@ -451,6 +473,7 @@ public class ServerOrderWithExtras : IId<int>
/// <summary>
/// Client version of the order - doesn't have Customer/RelatedCustomers properties.
/// </summary>
[AcBinarySerializable]
public class ClientOrderSimple : IId<int>
{
public int Id { get; set; }

View File

@ -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

View File

@ -7,6 +7,9 @@
<ItemGroup>
<ProjectReference Include="..\AyCode.Utils\AyCode.Utils.csproj" />
<ProjectReference Include="..\AyCode.Core.Serializers.SourceGenerator\AyCode.Core.Serializers.SourceGenerator.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>
<ItemGroup>

View File

@ -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")]

View File

@ -0,0 +1,17 @@
using System;
namespace AyCode.Core.Serializers.Attributes;
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// If this attribute is not present, the serializer falls back to the existing
/// compiled expression based approach which works for all types.
/// </remarks>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false, AllowMultiple = false)]
public sealed class AcBinarySerializableAttribute : Attribute
{
}

View File

@ -9,6 +9,9 @@ namespace AyCode.Core.Serializers.Binaries;
public static partial class AcBinaryDeserializer
{
/// <summary>
/// Binary deserialization context. Public for generated serializers.
/// </summary>
internal ref struct BinaryDeserializationContext
{
private readonly ReadOnlySpan<byte> _buffer;

View File

@ -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
/// </summary>
public BinaryPropertySetterInfo[] PropertiesArray { get; }
/// <summary>
/// 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.
/// </summary>
public bool HasGeneratedDeserializer { get; }
/// <summary>
/// The generated serializer type, if available.
/// Consumer code can use this to call the generated methods directly.
/// </summary>
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;
}
}
/// <summary>
@ -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;
/// <summary>
/// Finds the generated serializer type for the given type.
/// </summary>
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;
}
}
/// <summary>

View File

@ -44,6 +44,9 @@ public static partial class AcBinarySerializer
}
}
/// <summary>
/// Binary serialization context. Public for generated serializers.
/// </summary>
internal sealed class BinarySerializationContext : IDisposable
{
private const int MinBufferSize = 256;

View File

@ -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; }
/// <summary>
/// True if this type has a Source Generator generated serializer available.
/// </summary>
public bool HasGeneratedSerializer { get; }
/// <summary>
/// The generated serializer type, if available.
/// </summary>
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;
}
}