AyCode.Core/AyCode.Core.Serializers.Sou.../AcBinarySourceGenerator.Gen...

910 lines
51 KiB
C#

using System.Collections.Generic;
using System.Text;
namespace AyCode.Core.Serializers.SourceGenerator;
/// <summary>
/// Reader-side emit pass: generates the <c>IGeneratedBinaryReader</c> implementation for each
/// <c>[AcBinarySerializable]</c> type. Emits <c>ReadProperties</c> (inline property reads with marker
/// dispatch) and <c>ReadObject</c> (entry point with cache-index registration).
///
/// <para>Sub-passes:</para>
/// <list type="bullet">
/// <item><c>EmitReadProp</c> — per-property read emit (markerless + markered variants).</item>
/// <item><c>EmitReadString</c> — H2Q6 string-tier marker dispatch (FixStrAscii + tier-tables +
/// intern cases gated by <c>EnableInternStringFeature</c>).</item>
/// <item><c>EmitReadComplex</c> — Object / ObjectRef* / FixObj-slot dispatch for IId-typed children.</item>
/// <item><c>EmitReadCollection</c> / <c>EmitReadCollectionInline</c> / <c>EmitReadCollectionElement</c> /
/// <c>EmitReadNonComplexCollectionElement</c> — collection-shape inline reading.</item>
/// <item><c>EmitReadDictionary</c> / <c>EmitReadDictElement</c> — dict-shape inline reading.</item>
/// <item><c>EmitReadMarkeredValue</c> / <c>EmitReadMarkeredValueForKind</c> — primitive value-with-marker reads.</item>
/// <item><c>EmitReadMarkerless</c> — markerless primitive reads (FastMode + per-property markerless types).</item>
/// </list>
/// </summary>
public partial class AcBinarySourceGenerator
{
#region Reader Code Generation
/// <summary>
/// Generates the IGeneratedBinaryReader implementation for a type.
/// Phase 1: handles markerless path (no UseMetadata). UseMetadata/ChainMode → runtime fallback.
/// Eliminates: GetWrapper dictionary lookup, CreateInstance delegate, property setter delegates,
/// AccessorType switch dispatch, ReadValue dispatch table.
/// </summary>
private static string GenReader(SerializableClassInfo ci)
{
var sb = new StringBuilder(4096);
sb.AppendLine("// <auto-generated/>");
sb.AppendLine("#nullable enable");
sb.AppendLine("using System.Runtime.CompilerServices;");
sb.AppendLine("using AyCode.Core.Serializers.Binaries;");
sb.AppendLine();
if (!string.IsNullOrEmpty(ci.Namespace))
sb.AppendLine($"namespace {ci.Namespace};");
sb.AppendLine();
sb.AppendLine($"internal sealed class {ci.ClassName}_GeneratedReader : IGeneratedBinaryReader");
sb.AppendLine("{");
sb.AppendLine($" internal static readonly {ci.ClassName}_GeneratedReader Instance = new();");
sb.AppendLine();
// ReadProperties — reads all properties into an existing instance (mirrors WriteProperties)
// No depth safety net on deserialize: wire format is linear + finite, the serializer-side counter
// already prevents pathological depth in well-formed payloads.
sb.AppendLine(" public void ReadProperties<TInput>(object value, AcBinaryDeserializer.BinaryDeserializationContext<TInput> context)");
sb.AppendLine(" where TInput : struct, IBinaryInputBase");
sb.AppendLine(" {");
sb.AppendLine($" var obj = Unsafe.As<{ci.FullTypeName}>(value);");
// Emit property reads — markerless for primitive types, markered for the rest
foreach (var p in ci.Properties)
{
sb.AppendLine();
EmitReadProp(sb, p, " ", ci.EnableMetadata, ci.EnableInternString);
}
sb.AppendLine(" }");
sb.AppendLine();
// ReadObject — IGeneratedBinaryReader implementation (delegates to ReadProperties)
sb.AppendLine(" public object? ReadObject<TInput>(AcBinaryDeserializer.BinaryDeserializationContext<TInput> context, int cacheIndex)");
sb.AppendLine(" where TInput : struct, IBinaryInputBase");
sb.AppendLine(" {");
sb.AppendLine($" var obj = new {ci.FullTypeName}();");
sb.AppendLine(" if (cacheIndex >= 0)");
sb.AppendLine(" context.RegisterInternedValueAt(cacheIndex, obj);");
sb.AppendLine(" ReadProperties<TInput>(obj, context);");
sb.AppendLine(" return obj;");
sb.AppendLine(" }");
sb.AppendLine("}");
return sb.ToString();
}
/// <summary>
/// Emits inline read code for a single property.
/// Markerless types: read raw value directly (no type code in stream).
/// Markered types: read type code byte, then dispatch.
/// Mirrors the serializer's EmitProp symmetry.
/// </summary>
private static void EmitReadProp(StringBuilder sb, PropInfo p, string i, bool enableMetadata, bool enableInternString)
{
var a = $"obj.{p.Name}";
// Markerless types: read raw value directly — mirrors EmitMarkerless in writer
if (IsMarkerless(p.TypeKind))
{
if (p.TypeKind == PropertyTypeKind.Enum)
sb.AppendLine($"{i}{{ var ev = context.ReadVarInt(); {a} = Unsafe.As<int, {p.TypeNameForTypeof}>(ref ev); }}");
else
EmitReadMarkerless(sb, p.TypeKind, a, i);
return;
}
// ACCORE-BIN-T-K9M3 — caller-driven string marker dispatch. SGen-emit reads the marker byte
// locally + handles FastWire on a separate branch; BinaryDeserializationContext.TryReadStringProperty
// decodes every non-interning marker (FixStrAscii / StringAscii / StringSmall/Medium/Big / Null /
// StringEmpty) in one inlinable body. The 3 interning markers go through TryReadStringColdPath
// (AggressiveOptimization, Tier-1 direct). enableInternString gates the `|| TryReadStringColdPath`
// emit: interning-enabled types get the short-circuit; non-interning types omit the cold call
// entirely — the writer never produces interning markers for them, so TryReadStringProperty alone
// is total. PropertySkip / unknown → TryReadStringProperty returns false → property left at
// default (don't-touch contract preserved).
if (p.TypeKind == PropertyTypeKind.String)
{
sb.AppendLine($"{i}if (context.FastWire)");
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} {a} = context.ReadStringUtf16Markerless()!;");
sb.AppendLine($"{i}}}");
sb.AppendLine($"{i}else");
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} var tc_{p.Name} = context.ReadByte();");
sb.AppendLine($"{i} string? v_{p.Name};");
if (enableInternString)
sb.AppendLine($"{i} if (context.TryReadStringProperty(tc_{p.Name}, out v_{p.Name}) || context.TryReadStringColdPath(tc_{p.Name}, out v_{p.Name}))");
else
sb.AppendLine($"{i} if (context.TryReadStringProperty(tc_{p.Name}, out v_{p.Name}))");
sb.AppendLine($"{i} {{");
sb.AppendLine($"{i} {a} = v_{p.Name}!;");
sb.AppendLine($"{i} }}");
sb.AppendLine($"{i}}}");
return;
}
// Markered types: read type code, then dispatch
var tc = $"tc_{p.Name}";
sb.AppendLine($"{i}var {tc} = context.ReadByte();");
// PropertySkip → leave default
sb.AppendLine($"{i}if ({tc} != BinaryTypeCode.PropertySkip)");
sb.AppendLine($"{i}{{");
// Nullable value types
if (IsNullableVTKind(p.TypeKind))
{
sb.AppendLine($"{i} if ({tc} == BinaryTypeCode.Null) {{ /* null */ }}");
sb.AppendLine($"{i} else");
sb.AppendLine($"{i} {{");
EmitReadMarkeredValue(sb, Underlying(p.TypeKind), a, tc, i + " ", p, nullable: true);
sb.AppendLine($"{i} }}");
}
else
{
switch (p.TypeKind)
{
case PropertyTypeKind.String:
EmitReadString(sb, a, tc, i + " ", enableInternString);
break;
case PropertyTypeKind.Complex:
EmitReadComplex(sb, p, a, tc, i + " ");
break;
case PropertyTypeKind.Collection:
EmitReadCollection(sb, p, a, tc, i + " ", enableInternString);
break;
case PropertyTypeKind.Dictionary:
EmitReadDictionary(sb, p, a, tc, i + " ", enableInternString);
break;
default:
// Unknown markered type (char, sbyte, etc.) — rewind + runtime fallback
sb.AppendLine($"{i} context._position--;");
if (p.IsNullable)
sb.AppendLine($"{i} {a} = ({p.TypeNameForTypeof}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}));");
else
sb.AppendLine($"{i} {a} = ({p.TypeNameForTypeof})AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}))!;");
break;
}
}
sb.AppendLine($"{i}}}");
}
/// <summary>
/// Emits raw value read — no type code in stream. Mirrors EmitMarkerless exactly.
/// </summary>
private static void EmitReadMarkerless(StringBuilder sb, PropertyTypeKind k, string a, string i)
{
switch (k)
{
case PropertyTypeKind.Int32: sb.AppendLine($"{i}{a} = context.ReadVarInt();"); break;
case PropertyTypeKind.Int64: sb.AppendLine($"{i}{a} = context.ReadVarLong();"); break;
case PropertyTypeKind.Double: sb.AppendLine($"{i}{a} = context.ReadDoubleUnsafe();"); break;
case PropertyTypeKind.Single: sb.AppendLine($"{i}{a} = context.ReadSingleUnsafe();"); break;
case PropertyTypeKind.Decimal: sb.AppendLine($"{i}{a} = context.ReadDecimalUnsafe();"); break;
case PropertyTypeKind.DateTime: sb.AppendLine($"{i}{a} = context.ReadDateTimeUnsafe();"); break;
case PropertyTypeKind.Guid: sb.AppendLine($"{i}{a} = context.ReadGuidUnsafe();"); break;
case PropertyTypeKind.Byte: sb.AppendLine($"{i}{a} = context.ReadByte();"); break;
case PropertyTypeKind.Int16: sb.AppendLine($"{i}{a} = context.ReadInt16Unsafe();"); break;
case PropertyTypeKind.UInt16: sb.AppendLine($"{i}{a} = context.ReadUInt16Unsafe();"); break;
case PropertyTypeKind.UInt32: sb.AppendLine($"{i}{a} = context.ReadVarUInt();"); break;
case PropertyTypeKind.UInt64: sb.AppendLine($"{i}{a} = context.ReadVarULong();"); break;
case PropertyTypeKind.TimeSpan: sb.AppendLine($"{i}{a} = new System.TimeSpan(context.ReadRaw<long>());"); break;
case PropertyTypeKind.DateTimeOffset: sb.AppendLine($"{i}{a} = context.ReadDateTimeOffsetUnsafe();"); break;
case PropertyTypeKind.Boolean: sb.AppendLine($"{i}{a} = context.ReadByte() != 0;"); break;
}
}
/// <summary>
/// Emits inline string read from type code. Handles all H2Q6 (v3 wire format) string markers:
/// FixStr (short-form universal, 135-166), String (long-form universal, 167),
/// StringUtf16 (FastWire marker, 91),
/// StringInternFirstSmall/Medium (interning tiers, 104/105),
/// StringInterned (cache ref, 92), StringEmpty (93), Null.
///
/// FixStr is checked first as the hot path for short strings; non-ASCII
/// tier markers carry both <c>charLen</c> and <c>utf8Len</c> in fixed-width headers (1-pass decode).
/// </summary>
private static void EmitReadString(StringBuilder sb, string a, string tc, string i, bool enableInternString)
{
// FixStr is the hot path — short-form universal marker with charLength in the marker.
sb.AppendLine($"{i}if (BinaryTypeCode.IsFixStr({tc}))");
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} {a} = context.ReadUniversalFixStr({tc});");
sb.AppendLine($"{i}}}");
// Switch gives O(1) dispatch via JIT jump table for the remaining markers.
sb.AppendLine($"{i}else switch ({tc})");
sb.AppendLine($"{i}{{");
// Interning case (2nd+ occurrence ref) — only emit when EnableInternStringFeature is enabled
// on this type. When disabled, the writer never emits StringInterned markers for this type's
// properties, so the reader doesn't need to handle them. ACCORE-BIN-T-K9M3 Phase C.
if (enableInternString)
{
sb.AppendLine($"{i} case BinaryTypeCode.StringInterned:");
sb.AppendLine($"{i} {a} = context.GetInternedString((int)context.ReadVarUInt());");
sb.AppendLine($"{i} break;");
}
// StringUtf16 marker + String. Wire-decode body is shared with the runtime path
// (TypeReaderTable + cross-type populate) — see context.ReadStringUtf16Marker()
// and ReadUniversalLongString.
// These markers are feature-independent: writer emits them on any string property regardless of
// intern setting (intern is opt-in per-property via [AcStringIntern] + InternBit).
sb.AppendLine($"{i} case BinaryTypeCode.StringUtf16:");
sb.AppendLine($"{i} {a} = context.ReadStringUtf16Marker();");
sb.AppendLine($"{i} break;");
sb.AppendLine($"{i} case BinaryTypeCode.String:");
sb.AppendLine($"{i} {a} = context.ReadUniversalLongString();");
sb.AppendLine($"{i} break;");
// Interning first-occurrence cases — see comment above.
if (enableInternString)
{
sb.AppendLine($"{i} case BinaryTypeCode.StringInternFirstSmall:");
sb.AppendLine($"{i} {a} = context.ReadAndRegisterInternedStringSmall();");
sb.AppendLine($"{i} break;");
sb.AppendLine($"{i} case BinaryTypeCode.StringInternFirstMedium:");
sb.AppendLine($"{i} {a} = context.ReadAndRegisterInternedStringMedium();");
sb.AppendLine($"{i} break;");
}
sb.AppendLine($"{i} case BinaryTypeCode.Null:");
sb.AppendLine($"{i} {a} = null;");
sb.AppendLine($"{i} break;");
sb.AppendLine($"{i} case BinaryTypeCode.StringEmpty:");
sb.AppendLine($"{i} {a} = string.Empty;");
sb.AppendLine($"{i} break;");
sb.AppendLine($"{i}}}");
}
/// <summary>
/// Emits inline read for a Complex property.
/// SGen reader only runs in non-metadata mode → ObjectWithMetadata never appears.
/// Compile-time ChildNeedsRefScan eliminates ObjectRefFirst/ObjectRef when provably unused.
/// Non-nullable + no ref → ZERO branches (tc consumed but ignored).
/// No SGen → runtime fallback via ReadValueGenerated.
/// </summary>
private static void EmitReadComplex(StringBuilder sb, PropInfo p, string a, string tc, string i)
{
if (!p.HasGeneratedWriter)
{
// No SGen reader — runtime fallback (rewind + ReadValueGenerated)
if (p.IsNullable)
{
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Null) {a} = null;");
sb.AppendLine($"{i}else");
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} context._position--;");
sb.AppendLine($"{i} {a} = ({p.TypeNameForTypeof}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}));");
sb.AppendLine($"{i}}}");
}
else
{
sb.AppendLine($"{i}context._position--;");
sb.AppendLine($"{i}{a} = ({p.TypeNameForTypeof})AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}))!;");
}
return;
}
var reader = p.WriterClassName!.Replace("_GeneratedWriter", "_GeneratedReader");
var cast = $"({p.TypeNameForTypeof})";
// Ref-aware switch decision routed through RefAwareEmitPredicate — single source of truth shared
// with the writer-side EmitDirectCollectionWrite + the sibling EmitReadCollectionElement. The
// decision depends EXCLUSIVELY on the child compile-time fact `ChildNeedsRefScan` — the parent
// EnableRefHandlingFeature flag is NOT a factor here (it governs only the parent's SELF-tracking
// emit in the scan pass, not the marker dispatch for child property reads). Asymmetry-bug fix:
// see AcBinarySerializerIIdReferenceTests.Serialize_RefMarkerCollectionElement_ParentRefHandlingFeatureOff_DriftReproduction.
if (!RefAwareEmitPredicate.ChildEmitsRefMarker(p))
{
// Compile-time proven: child never tracked → only Object (+ Null for nullable) in stream
// Inline: parent creates instance, calls ReadProperties directly (mirrors EmitDirectObjectWrite)
// FixObj slot bytes (0..SlotCount-1) are also valid markers here — populate slot cache
// to keep _nextRuntimeSlot in sync with the serializer's _nextTypeSlot counter.
if (p.IsNullable)
{
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Null) {{ /* null */ }}");
sb.AppendLine($"{i}else");
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} if ({tc} < BinaryTypeCode.Object) {{ context.GetWrapper(typeof({p.TypeNameForTypeof}), {tc}); if ({tc} >= context._nextRuntimeSlot) context._nextRuntimeSlot = {tc} + 1; }}");
sb.AppendLine($"{i} var rc_{p.Name} = new {p.TypeNameForTypeof}();");
sb.AppendLine($"{i} {reader}.Instance.ReadProperties(rc_{p.Name}, context);");
sb.AppendLine($"{i} {a} = rc_{p.Name};");
sb.AppendLine($"{i}}}");
}
else
{
// ZERO branches — tc is always Object or FixObj
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} if ({tc} < BinaryTypeCode.Object) {{ context.GetWrapper(typeof({p.TypeNameForTypeof}), {tc}); if ({tc} >= context._nextRuntimeSlot) context._nextRuntimeSlot = {tc} + 1; }}");
sb.AppendLine($"{i} var rc_{p.Name} = new {p.TypeNameForTypeof}();");
sb.AppendLine($"{i} {reader}.Instance.ReadProperties(rc_{p.Name}, context);");
sb.AppendLine($"{i} {a} = rc_{p.Name};");
sb.AppendLine($"{i}}}");
}
}
else
{
// Ref tracking possible — switch on tc (Object / ObjectRefFirst / [Null] / ObjectRef / <Object).
// The 4 known TypeCode constants are emitted as switch cases — the JIT compiles them as a
// jump-table for O(1) dispatch (vs the previous if-else chain's sequential ==-compares).
// The polymorphic FixObj range-check (tc < Object) goes into the default branch — runtime
// bridge path is rare on a typical SGen graph, so default fall-through is acceptable.
// Inline: parent creates instance + handles cache registration.
sb.AppendLine($"{i}switch ({tc})");
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} case BinaryTypeCode.Object:");
sb.AppendLine($"{i} {{");
sb.AppendLine($"{i} var rc_{p.Name} = new {p.TypeNameForTypeof}();");
sb.AppendLine($"{i} {reader}.Instance.ReadProperties(rc_{p.Name}, context);");
sb.AppendLine($"{i} {a} = rc_{p.Name};");
sb.AppendLine($"{i} break;");
sb.AppendLine($"{i} }}");
sb.AppendLine($"{i} case BinaryTypeCode.ObjectRefFirst:");
sb.AppendLine($"{i} {{");
sb.AppendLine($"{i} var ci_{p.Name} = (int)context.ReadVarUInt();");
sb.AppendLine($"{i} var rc_{p.Name} = new {p.TypeNameForTypeof}();");
sb.AppendLine($"{i} context.RegisterInternedValueAt(ci_{p.Name}, rc_{p.Name});");
sb.AppendLine($"{i} {reader}.Instance.ReadProperties(rc_{p.Name}, context);");
sb.AppendLine($"{i} {a} = rc_{p.Name};");
sb.AppendLine($"{i} break;");
sb.AppendLine($"{i} }}");
if (p.IsNullable)
sb.AppendLine($"{i} case BinaryTypeCode.Null: break;");
sb.AppendLine($"{i} case BinaryTypeCode.ObjectRef:");
sb.AppendLine($"{i} {a} = {cast}context.GetInternedObject((int)context.ReadVarUInt())!;");
sb.AppendLine($"{i} break;");
// FixObj slot (0..SlotCount-1): same type via FixObj marker (non-meta, non-ref mode).
// Populate slot cache to keep _nextRuntimeSlot in sync with the serializer.
sb.AppendLine($"{i} default:");
sb.AppendLine($"{i} if ({tc} < BinaryTypeCode.Object)");
sb.AppendLine($"{i} {{");
sb.AppendLine($"{i} context.GetWrapper(typeof({p.TypeNameForTypeof}), {tc});");
sb.AppendLine($"{i} if ({tc} >= context._nextRuntimeSlot) context._nextRuntimeSlot = {tc} + 1;");
sb.AppendLine($"{i} var rc_{p.Name} = new {p.TypeNameForTypeof}();");
sb.AppendLine($"{i} {reader}.Instance.ReadProperties(rc_{p.Name}, context);");
sb.AppendLine($"{i} {a} = rc_{p.Name};");
sb.AppendLine($"{i} }}");
sb.AppendLine($"{i} break;");
sb.AppendLine($"{i}}}");
}
}
/// <summary>
/// Returns true when collection element reading can be inlined (no runtime ReadValue dispatch needed).
/// </summary>
private static bool CanInlineCollectionRead(PropInfo p)
{
if (p.ElementKind == PropertyTypeKind.Complex && p.ElementHasGeneratedWriter) return true;
if (p.ElementKind == PropertyTypeKind.String) return true;
if (p.ElementKind == PropertyTypeKind.Enum) return true;
if (IsMarkerless(p.ElementKind)) return true; // all primitives
return false;
}
/// <summary>
/// Emits inline read for a Collection property.
/// Known collection kind + inlineable element → inline Array loop with direct element reads.
/// Else → runtime fallback via ReadValueGenerated.
/// </summary>
private static void EmitReadCollection(StringBuilder sb, PropInfo p, string a, string tc, string i, bool enableInternString)
{
// Check if we can inline: known collection shape + inlineable element type
if (p.CollectionKind != null && CanInlineCollectionRead(p))
{
EmitReadCollectionInline(sb, p, a, tc, i, enableInternString);
return;
}
// Runtime fallback
if (p.IsNullable)
{
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Null) {a} = null;");
sb.AppendLine($"{i}else");
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} context._position--;");
sb.AppendLine($"{i} {a} = ({p.TypeNameForTypeof}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}));");
sb.AppendLine($"{i}}}");
}
else
{
sb.AppendLine($"{i}context._position--;");
sb.AppendLine($"{i}{a} = ({p.TypeNameForTypeof})AcBinaryDeserializer.ReadValueGenerated(context, typeof({p.TypeNameForTypeof}))!;");
}
}
/// <summary>
/// Emits inline read for a Dictionary property.
/// Wire format: [Dictionary][VarUInt count][key₁ value₁ key₂ value₂ ...].
/// Keys and values are read inline when their types are known (primitive/string/Complex+SGen).
/// </summary>
private static void EmitReadDictionary(StringBuilder sb, PropInfo p, string a, string tc, string i, bool enableInternString)
{
var s = p.Name;
var keyType = p.DictKeyTypeName ?? "object";
var valType = p.DictValueTypeName ?? "object";
// Can we inline key/value reads?
var canInlineKey = p.DictKeyKind == PropertyTypeKind.String || IsMarkerless(p.DictKeyKind) || p.DictKeyKind == PropertyTypeKind.Enum;
var canInlineValue = p.DictValueKind == PropertyTypeKind.String || IsMarkerless(p.DictValueKind) || p.DictValueKind == PropertyTypeKind.Enum
|| (p.DictValueKind == PropertyTypeKind.Complex && p.DictValueHasGeneratedWriter);
var canInline = canInlineKey || canInlineValue; // partial inline is still beneficial
if (p.IsNullable)
{
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Null) {a} = null;");
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.Dictionary)");
}
else
{
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Dictionary)");
}
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} var cnt_{s} = (int)context.ReadVarUInt();");
sb.AppendLine($"{i} var dict_{s} = new System.Collections.Generic.Dictionary<{keyType}, {valType}>(cnt_{s});");
sb.AppendLine($"{i} for (var di_{s} = 0; di_{s} < cnt_{s}; di_{s}++)");
sb.AppendLine($"{i} {{");
// Read key
if (canInlineKey)
EmitReadDictElement(sb, p.DictKeyKind, keyType, $"dk_{s}", s, i + " ", null, false, enableInternString);
else
sb.AppendLine($"{i} var dk_{s} = ({keyType})AcBinaryDeserializer.ReadValueGenerated(context, typeof({keyType}))!;");
// Read value
if (p.DictValueKind == PropertyTypeKind.Complex && p.DictValueHasGeneratedWriter)
{
var valReader = p.DictValueWriterClassName!.Replace("_GeneratedWriter", "_GeneratedReader");
var vtc = $"vtc_{s}";
sb.AppendLine($"{i} var {vtc} = context.ReadByte();");
sb.AppendLine($"{i} {valType}? dv_{s} = null;");
sb.AppendLine($"{i} if ({vtc} == BinaryTypeCode.Object)");
sb.AppendLine($"{i} {{");
sb.AppendLine($"{i} var rv_{s} = new {valType}();");
sb.AppendLine($"{i} {valReader}.Instance.ReadProperties(rv_{s}, context);");
sb.AppendLine($"{i} dv_{s} = rv_{s};");
sb.AppendLine($"{i} }}");
// ObjectRefFirst / ObjectRef cases — routed through RefAwareEmitPredicate. Single source of
// truth shared with EmitReadComplex / EmitReadCollectionElement / EmitDirectCollectionWrite.
// The decision depends EXCLUSIVELY on the dict-value compile-time fact `DictValueNeedsRefScan`
// — the parent EnableRefHandlingFeature flag is NOT a factor here (it governs only the parent's
// SELF-tracking emit in the scan pass, GenWriter.cs:140). Symmetric with the writer-side
// dict-value emit. Asymmetry-bug fix: see AcBinarySerializerIIdReferenceTests
// .Serialize_RefMarkerCollectionElement_ParentRefHandlingFeatureOff_DriftReproduction.
if (RefAwareEmitPredicate.DictValueEmitsRefMarker(p))
{
sb.AppendLine($"{i} else if ({vtc} == BinaryTypeCode.ObjectRefFirst)");
sb.AppendLine($"{i} {{");
sb.AppendLine($"{i} var rci_{s} = (int)context.ReadVarUInt();");
sb.AppendLine($"{i} var rv_{s} = new {valType}();");
sb.AppendLine($"{i} context.RegisterInternedValueAt(rci_{s}, rv_{s});");
sb.AppendLine($"{i} {valReader}.Instance.ReadProperties(rv_{s}, context);");
sb.AppendLine($"{i} dv_{s} = rv_{s};");
sb.AppendLine($"{i} }}");
sb.AppendLine($"{i} else if ({vtc} == BinaryTypeCode.ObjectRef)");
sb.AppendLine($"{i} dv_{s} = ({valType})context.GetInternedObject((int)context.ReadVarUInt())!;");
}
sb.AppendLine($"{i} else if ({vtc} != BinaryTypeCode.Null)");
sb.AppendLine($"{i} {{");
sb.AppendLine($"{i} context._position--;");
sb.AppendLine($"{i} dv_{s} = ({valType}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({valType}));");
sb.AppendLine($"{i} }}");
}
else if (canInlineValue)
EmitReadDictElement(sb, p.DictValueKind, valType, $"dv_{s}", s, i + " ", null, true, enableInternString);
else
sb.AppendLine($"{i} var dv_{s} = ({valType}?)AcBinaryDeserializer.ReadValueGenerated(context, typeof({valType}));");
// Add to dictionary
sb.AppendLine($"{i} if (dk_{s} != null) dict_{s}[dk_{s}] = dv_{s}!;");
sb.AppendLine($"{i} }}");
sb.AppendLine($"{i} {a} = dict_{s};");
sb.AppendLine($"{i}}}");
}
/// <summary>
/// Emits inline read for a single dictionary key or value element.
/// Reads type code byte, then dispatches based on element kind.
/// </summary>
private static void EmitReadDictElement(StringBuilder sb, PropertyTypeKind kind, string typeName, string varName, string propSuffix, string i, PropInfo? p, bool isRefType, bool enableInternString)
{
var etc = $"{varName}_tc";
sb.AppendLine($"{i}var {etc} = context.ReadByte();");
if (kind == PropertyTypeKind.String)
{
sb.AppendLine($"{i}{typeName}? {varName} = null;");
EmitReadString(sb, varName, etc, i, enableInternString);
}
else if (kind == PropertyTypeKind.Enum)
{
sb.AppendLine($"{i}{typeName} {varName} = default;");
sb.AppendLine($"{i}if ({etc} == BinaryTypeCode.Enum)");
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} var eb = context.ReadByte();");
sb.AppendLine($"{i} int eiv;");
sb.AppendLine($"{i} if (BinaryTypeCode.IsTinyInt(eb)) eiv = BinaryTypeCode.DecodeTinyInt(eb);");
sb.AppendLine($"{i} else eiv = context.ReadVarInt();");
sb.AppendLine($"{i} {varName} = ({typeName})(object)eiv;");
sb.AppendLine($"{i}}}");
sb.AppendLine($"{i}else if (BinaryTypeCode.IsTinyInt({etc})) {varName} = ({typeName})(object)BinaryTypeCode.DecodeTinyInt({etc});");
}
else
{
// Primitive value type — never nullable
sb.AppendLine($"{i}{typeName} {varName} = default;");
EmitReadMarkeredValueForKind(sb, kind, varName, etc, i);
}
}
/// <summary>
/// Emits markered value read by kind only (no PropInfo needed). For dict key/value inline reads.
/// </summary>
private static void EmitReadMarkeredValueForKind(StringBuilder sb, PropertyTypeKind k, string a, string tc, string i)
{
switch (k)
{
case PropertyTypeKind.Int32:
sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {a} = BinaryTypeCode.DecodeTinyInt({tc});");
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.Int32) {a} = context.ReadVarInt();");
break;
case PropertyTypeKind.Int64:
sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {a} = BinaryTypeCode.DecodeTinyInt({tc});");
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.Int32) {a} = context.ReadVarInt();");
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.Int64) {a} = context.ReadVarLong();");
break;
case PropertyTypeKind.Boolean:
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.True) {a} = true;");
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.False) {a} = false;");
break;
case PropertyTypeKind.Double:
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Float64) {a} = context.ReadDoubleUnsafe();");
break;
case PropertyTypeKind.Single:
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Float32) {a} = context.ReadSingleUnsafe();");
break;
case PropertyTypeKind.Decimal:
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Decimal) {a} = context.ReadDecimalUnsafe();");
break;
case PropertyTypeKind.DateTime:
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.DateTime) {a} = context.ReadDateTimeUnsafe();");
break;
case PropertyTypeKind.Guid:
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Guid) {a} = context.ReadGuidUnsafe();");
break;
case PropertyTypeKind.Byte:
sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {a} = (byte)BinaryTypeCode.DecodeTinyInt({tc});");
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.UInt8) {a} = context.ReadByte();");
break;
case PropertyTypeKind.Int16:
sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {a} = (short)BinaryTypeCode.DecodeTinyInt({tc});");
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.Int16) {a} = context.ReadInt16Unsafe();");
break;
case PropertyTypeKind.UInt16:
sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {a} = (ushort)BinaryTypeCode.DecodeTinyInt({tc});");
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.UInt16) {a} = context.ReadUInt16Unsafe();");
break;
case PropertyTypeKind.UInt32:
sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {a} = (uint)BinaryTypeCode.DecodeTinyInt({tc});");
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.UInt32) {a} = context.ReadVarUInt();");
break;
case PropertyTypeKind.UInt64:
sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {a} = (ulong)BinaryTypeCode.DecodeTinyInt({tc});");
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.UInt64) {a} = context.ReadVarULong();");
break;
case PropertyTypeKind.TimeSpan:
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.TimeSpan) {a} = context.ReadTimeSpanUnsafe();");
break;
case PropertyTypeKind.DateTimeOffset:
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.DateTimeOffset) {a} = context.ReadDateTimeOffsetUnsafe();");
break;
}
}
/// <summary>
/// Emits inline collection read: Array marker already consumed as tc.
/// Reads count + loops with direct element reads (Complex with SGen, or primitive/string/enum).
/// Eliminates per-element: ReadValue dispatch, ReadObjectCore dict lookup, Activator.CreateInstance.
/// </summary>
private static void EmitReadCollectionInline(StringBuilder sb, PropInfo p, string a, string tc, string i, bool enableInternString)
{
var isComplexElement = p.ElementKind == PropertyTypeKind.Complex && p.ElementHasGeneratedWriter;
var elemType = p.ElementFullTypeName!;
var s = p.Name;
// Null check
if (p.IsNullable)
{
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Null) {a} = null;");
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.Array)");
}
else
{
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Array)");
}
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} var cnt_{s} = (int)context.ReadVarUInt();");
// Create collection + loop based on kind
if (p.CollectionKind == "Array")
{
sb.AppendLine($"{i} var col_{s} = new {elemType}[cnt_{s}];");
sb.AppendLine($"{i} for (var ri_{s} = 0; ri_{s} < cnt_{s}; ri_{s}++)");
sb.AppendLine($"{i} {{");
if (isComplexElement)
EmitReadCollectionElement(sb, p.ElementWriterClassName!.Replace("_GeneratedWriter", "_GeneratedReader"), elemType, $"({elemType})", $"ri_{s}", s, i + " ", isArray: true, p.ElementNeedsRefScan, enableInternString);
else
EmitReadNonComplexCollectionElement(sb, p, $"ri_{s}", s, i + " ", isArray: true, null, enableInternString);
sb.AppendLine($"{i} }}");
}
else if (p.CollectionKind == "Counted" && p.CollectionAddMethod != null)
{
// Concrete custom collection — use actual type + correct add method
if (p.CollectionHasCapacityCtor)
sb.AppendLine($"{i} var col_{s} = new {p.TypeNameForTypeof}(cnt_{s});");
else
sb.AppendLine($"{i} var col_{s} = new {p.TypeNameForTypeof}();");
sb.AppendLine($"{i} for (var ri_{s} = 0; ri_{s} < cnt_{s}; ri_{s}++)");
sb.AppendLine($"{i} {{");
if (isComplexElement)
EmitReadCollectionElement(sb, p.ElementWriterClassName!.Replace("_GeneratedWriter", "_GeneratedReader"), elemType, $"({elemType})", $"ri_{s}", s, i + " ", isArray: false, p.ElementNeedsRefScan, enableInternString, p.CollectionAddMethod);
else
EmitReadNonComplexCollectionElement(sb, p, $"ri_{s}", s, i + " ", isArray: false, p.CollectionAddMethod, enableInternString);
sb.AppendLine($"{i} }}");
}
else // List, IndexedCollection, Counted-interface → List<T> with Add
{
sb.AppendLine($"{i} var col_{s} = new System.Collections.Generic.List<{elemType}>(cnt_{s});");
sb.AppendLine($"{i} for (var ri_{s} = 0; ri_{s} < cnt_{s}; ri_{s}++)");
sb.AppendLine($"{i} {{");
if (isComplexElement)
EmitReadCollectionElement(sb, p.ElementWriterClassName!.Replace("_GeneratedWriter", "_GeneratedReader"), elemType, $"({elemType})", $"ri_{s}", s, i + " ", isArray: false, p.ElementNeedsRefScan, enableInternString);
else
EmitReadNonComplexCollectionElement(sb, p, $"ri_{s}", s, i + " ", isArray: false, null, enableInternString);
sb.AppendLine($"{i} }}");
}
sb.AppendLine($"{i} {a} = col_{s};");
sb.AppendLine($"{i}}}");
}
/// <summary>
/// Emits per-element read inside collection loop.
/// SGen reader = non-metadata mode → no ObjectWithMetadata fallback.
/// !needsRefScan → only Object/Null possible → 1 branch per element.
/// </summary>
private static void EmitReadCollectionElement(StringBuilder sb, string reader, string elemTypeName, string elemCast, string indexVar, string propSuffix, string i, bool isArray, bool needsRefScan, bool enableInternString, string? addMethod = null)
{
var etc = $"etc_{propSuffix}";
sb.AppendLine($"{i}var {etc} = context.ReadByte();");
var addCall = addMethod ?? "Add";
var assignNull = isArray ? $"col_{propSuffix}[{indexVar}] = null!;" : $"col_{propSuffix}.{addCall}(null!);";
var assignExpr = isArray ? $"col_{propSuffix}[{indexVar}] = re_{propSuffix};" : $"col_{propSuffix}.{addCall}(re_{propSuffix});";
// Ref-aware switch decision routed through RefAwareEmitPredicate — single source of truth shared
// with the writer-side EmitDirectCollectionWrite + EmitReadComplex. The decision depends
// EXCLUSIVELY on the element compile-time fact `needsRefScan` — the parent EnableRefHandlingFeature
// flag is NOT a factor here. Asymmetry-bug fix:
// see AcBinarySerializerIIdReferenceTests.Serialize_RefMarkerCollectionElement_ParentRefHandlingFeatureOff_DriftReproduction.
if (!RefAwareEmitPredicate.ElementEmitsRefMarker(needsRefScan))
{
// No ref tracking → only Object, FixObj or Null in stream — inline ReadProperties
// FixObj slot: populate slot cache to keep _nextRuntimeSlot in sync.
sb.AppendLine($"{i}if ({etc} == BinaryTypeCode.Null) {{ {assignNull} }}");
sb.AppendLine($"{i}else");
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} if ({etc} < BinaryTypeCode.Object) {{ context.GetWrapper(typeof({elemTypeName}), {etc}); if ({etc} >= context._nextRuntimeSlot) context._nextRuntimeSlot = {etc} + 1; }}");
sb.AppendLine($"{i} var re_{propSuffix} = new {elemTypeName}();");
sb.AppendLine($"{i} {reader}.Instance.ReadProperties(re_{propSuffix}, context);");
sb.AppendLine($"{i} {assignExpr}");
sb.AppendLine($"{i}}}");
}
else
{
// Switch on etc (Object / ObjectRefFirst / Null / ObjectRef / <Object). The JIT emits the
// 4 known TypeCode constants as a jump-table (O(1) dispatch); the polymorphic FixObj
// range-check (etc < Object) goes into the default branch. Object hot-path stays first.
sb.AppendLine($"{i}switch ({etc})");
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} case BinaryTypeCode.Object:");
sb.AppendLine($"{i} {{");
sb.AppendLine($"{i} var re_{propSuffix} = new {elemTypeName}();");
sb.AppendLine($"{i} {reader}.Instance.ReadProperties(re_{propSuffix}, context);");
sb.AppendLine($"{i} {assignExpr}");
sb.AppendLine($"{i} break;");
sb.AppendLine($"{i} }}");
sb.AppendLine($"{i} case BinaryTypeCode.ObjectRefFirst:");
sb.AppendLine($"{i} {{");
sb.AppendLine($"{i} var ci_{propSuffix} = (int)context.ReadVarUInt();");
sb.AppendLine($"{i} var re_{propSuffix} = new {elemTypeName}();");
sb.AppendLine($"{i} context.RegisterInternedValueAt(ci_{propSuffix}, re_{propSuffix});");
sb.AppendLine($"{i} {reader}.Instance.ReadProperties(re_{propSuffix}, context);");
sb.AppendLine($"{i} {assignExpr}");
sb.AppendLine($"{i} break;");
sb.AppendLine($"{i} }}");
sb.AppendLine($"{i} case BinaryTypeCode.Null:");
sb.AppendLine($"{i} {assignNull}");
sb.AppendLine($"{i} break;");
sb.AppendLine($"{i} case BinaryTypeCode.ObjectRef:");
if (isArray)
sb.AppendLine($"{i} col_{propSuffix}[{indexVar}] = {elemCast}context.GetInternedObject((int)context.ReadVarUInt())!;");
else
sb.AppendLine($"{i} col_{propSuffix}.{addCall}({elemCast}context.GetInternedObject((int)context.ReadVarUInt())!);");
sb.AppendLine($"{i} break;");
// FixObj slot (0..SlotCount-1): same type via FixObj marker.
// Populate slot cache to keep _nextRuntimeSlot in sync with the serializer.
sb.AppendLine($"{i} default:");
sb.AppendLine($"{i} if ({etc} < BinaryTypeCode.Object)");
sb.AppendLine($"{i} {{");
sb.AppendLine($"{i} context.GetWrapper(typeof({elemTypeName}), {etc});");
sb.AppendLine($"{i} if ({etc} >= context._nextRuntimeSlot) context._nextRuntimeSlot = {etc} + 1;");
sb.AppendLine($"{i} var re_{propSuffix} = new {elemTypeName}();");
sb.AppendLine($"{i} {reader}.Instance.ReadProperties(re_{propSuffix}, context);");
sb.AppendLine($"{i} {assignExpr}");
sb.AppendLine($"{i} }}");
sb.AppendLine($"{i} break;");
sb.AppendLine($"{i}}}");
}
}
/// <summary>
/// Emits per-element read for non-Complex collection elements (String, primitive, Enum).
/// Reads type code byte, then dispatches based on ElementKind.
/// </summary>
private static void EmitReadNonComplexCollectionElement(StringBuilder sb, PropInfo p, string indexVar, string propSuffix, string i, bool isArray, string? addMethod, bool enableInternString)
{
var addCall = addMethod ?? "Add";
var elemType = p.ElementFullTypeName!;
var colRef = $"col_{propSuffix}";
// String element FastWire markerless fast-path — same wire as property-level (int32 sentinel header).
// All FastWire string writes funnel through `WriteStringWithDispatch.FastWire = WriteStringUtf16Markerless`,
// so collection elements use the same markerless format. Skips the etc-read entirely in FastWire mode.
if (p.ElementKind == PropertyTypeKind.String)
{
var tempVar = $"sv_{propSuffix}";
sb.AppendLine($"{i}string? {tempVar};");
sb.AppendLine($"{i}if (context.FastWire)");
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} {tempVar} = context.ReadStringUtf16Markerless();");
sb.AppendLine($"{i}}}");
sb.AppendLine($"{i}else");
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} var etc_{propSuffix} = context.ReadByte();");
sb.AppendLine($"{i} {tempVar} = null;");
EmitReadString(sb, tempVar, $"etc_{propSuffix}", i + " ", enableInternString);
sb.AppendLine($"{i}}}");
if (isArray)
sb.AppendLine($"{i}{colRef}[{indexVar}] = {tempVar}!;");
else
sb.AppendLine($"{i}{colRef}.{addCall}({tempVar}!);");
return;
}
var etc = $"etc_{propSuffix}";
sb.AppendLine($"{i}var {etc} = context.ReadByte();");
if (p.ElementKind == PropertyTypeKind.Enum)
{
// Enum element: Enum marker or TinyInt
var tempVar = $"ev_{propSuffix}";
sb.AppendLine($"{i}{elemType} {tempVar} = default;");
sb.AppendLine($"{i}if ({etc} == BinaryTypeCode.Enum)");
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} var eb = context.ReadByte();");
sb.AppendLine($"{i} int eiv;");
sb.AppendLine($"{i} if (BinaryTypeCode.IsTinyInt(eb)) eiv = BinaryTypeCode.DecodeTinyInt(eb);");
sb.AppendLine($"{i} else eiv = context.ReadVarInt();");
sb.AppendLine($"{i} {tempVar} = ({elemType})(object)eiv;");
sb.AppendLine($"{i}}}");
sb.AppendLine($"{i}else if (BinaryTypeCode.IsTinyInt({etc})) {tempVar} = ({elemType})(object)BinaryTypeCode.DecodeTinyInt({etc});");
if (isArray)
sb.AppendLine($"{i}{colRef}[{indexVar}] = {tempVar};");
else
sb.AppendLine($"{i}{colRef}.{addCall}({tempVar});");
}
else
{
// Primitive element: read markered value
var tempVar = $"pv_{propSuffix}";
sb.AppendLine($"{i}{elemType} {tempVar} = default;");
// Create a minimal PropInfo-like context for EmitReadMarkeredValue
EmitReadMarkeredValue(sb, p.ElementKind, tempVar, etc, i, p, nullable: false);
if (isArray)
sb.AppendLine($"{i}{colRef}[{indexVar}] = {tempVar};");
else
sb.AppendLine($"{i}{colRef}.{addCall}({tempVar});");
}
}
/// <summary>
/// Emits markered value read for primitive types (with type code already read).
/// Handles TinyInt encoding for integer types.
/// </summary>
private static void EmitReadMarkeredValue(StringBuilder sb, PropertyTypeKind k, string a, string tc, string i, PropInfo p, bool nullable)
{
var assign = nullable ? $"{a} = " : $"{a} = ";
switch (k)
{
case PropertyTypeKind.Int32:
sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {assign}BinaryTypeCode.DecodeTinyInt({tc});");
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.Int32) {assign}context.ReadVarInt();");
break;
case PropertyTypeKind.Int64:
sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {assign}BinaryTypeCode.DecodeTinyInt({tc});");
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.Int32) {assign}context.ReadVarInt();");
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.Int64) {assign}context.ReadVarLong();");
break;
case PropertyTypeKind.Boolean:
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.True) {assign}true;");
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.False) {assign}false;");
break;
case PropertyTypeKind.Double:
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Float64) {assign}context.ReadDoubleUnsafe();");
break;
case PropertyTypeKind.Single:
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Float32) {assign}context.ReadSingleUnsafe();");
break;
case PropertyTypeKind.Decimal:
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Decimal) {assign}context.ReadDecimalUnsafe();");
break;
case PropertyTypeKind.DateTime:
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.DateTime) {assign}context.ReadDateTimeUnsafe();");
break;
case PropertyTypeKind.Guid:
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Guid) {assign}context.ReadGuidUnsafe();");
break;
case PropertyTypeKind.Byte:
sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {assign}(byte)BinaryTypeCode.DecodeTinyInt({tc});");
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.UInt8) {assign}context.ReadByte();");
break;
case PropertyTypeKind.Int16:
sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {assign}(short)BinaryTypeCode.DecodeTinyInt({tc});");
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.Int16) {assign}context.ReadInt16Unsafe();");
break;
case PropertyTypeKind.UInt16:
sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {assign}(ushort)BinaryTypeCode.DecodeTinyInt({tc});");
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.UInt16) {assign}context.ReadUInt16Unsafe();");
break;
case PropertyTypeKind.UInt32:
sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {assign}(uint)BinaryTypeCode.DecodeTinyInt({tc});");
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.UInt32) {assign}context.ReadVarUInt();");
break;
case PropertyTypeKind.UInt64:
sb.AppendLine($"{i}if (BinaryTypeCode.IsTinyInt({tc})) {assign}(ulong)BinaryTypeCode.DecodeTinyInt({tc});");
sb.AppendLine($"{i}else if ({tc} == BinaryTypeCode.UInt64) {assign}context.ReadVarULong();");
break;
case PropertyTypeKind.Enum:
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.Enum)");
sb.AppendLine($"{i}{{");
sb.AppendLine($"{i} var eb = context.ReadByte();");
sb.AppendLine($"{i} int ev;");
sb.AppendLine($"{i} if (BinaryTypeCode.IsTinyInt(eb)) ev = BinaryTypeCode.DecodeTinyInt(eb);");
sb.AppendLine($"{i} else ev = context.ReadVarInt();");
sb.AppendLine($"{i} {assign}({p.TypeNameForTypeof})(object)ev;");
sb.AppendLine($"{i}}}");
sb.AppendLine($"{i}else if (BinaryTypeCode.IsTinyInt({tc})) {assign}({p.TypeNameForTypeof})(object)BinaryTypeCode.DecodeTinyInt({tc});");
break;
case PropertyTypeKind.TimeSpan:
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.TimeSpan) {assign}context.ReadTimeSpanUnsafe();");
break;
case PropertyTypeKind.DateTimeOffset:
sb.AppendLine($"{i}if ({tc} == BinaryTypeCode.DateTimeOffset) {assign}context.ReadDateTimeOffsetUnsafe();");
break;
}
}
#endregion
}