diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.Populate.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.Populate.cs
index 14372d0..f291741 100644
--- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.Populate.cs
+++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.Populate.cs
@@ -169,20 +169,21 @@ public static partial class AcBinaryDeserializer
{
var context = state.Context;
var target = state.Target;
- var peekCode = context.PeekByte();
- // Nincs megfelelő target property → skip
+ // Nincs megfelelő target property → skip (SkipValue reads its own marker byte)
if (propInfo == null)
{
SkipValue(context, state.Metadata);
return;
}
- // Skip marker - property has default/null value
- if (peekCode == BinaryTypeCode.PropertySkip)
- {
- context.ReadByte(); // consume Skip marker
+ // Read marker once — eliminates redundant PeekByte + ReadByte boundary checks.
+ // All branches below receive the already-consumed typeCode.
+ var typeCode = context.ReadByte();
+ // Skip marker - property has default/null value
+ if (typeCode == BinaryTypeCode.PropertySkip)
+ {
// Populate mode: overwrite with default (existing object may have non-default values)
// Deserialize mode: skip write (new object already has defaults from CreateInstance)
if (!state.SkipDefaultWrite)
@@ -193,9 +194,8 @@ public static partial class AcBinaryDeserializer
}
// Null values - always set
- if (peekCode == BinaryTypeCode.Null)
+ if (typeCode == BinaryTypeCode.Null)
{
- context.ReadByte(); // consume Null marker
propInfo.SetValue(target, null);
return;
}
@@ -203,13 +203,11 @@ public static partial class AcBinaryDeserializer
var nextDepth = state.NextDepth;
// Handle collections
- if (peekCode == BinaryTypeCode.Array && propInfo.IsCollection)
+ if (typeCode == BinaryTypeCode.Array && propInfo.IsCollection)
{
var existingCollection = propInfo.GetValue(target);
if (existingCollection is IList existingList)
{
- context.ReadByte(); // consume Array marker
-
// Merge mode with IId collection: use merge logic
if (state.IsMergeMode && propInfo.IsIIdCollection)
{
@@ -225,12 +223,13 @@ public static partial class AcBinaryDeserializer
}
// Handle nested complex objects - reuse existing if available
- if ((peekCode == BinaryTypeCode.Object || peekCode == BinaryTypeCode.ObjectWithMetadata) && propInfo.IsComplexType)
+ if ((typeCode == BinaryTypeCode.Object || typeCode == BinaryTypeCode.ObjectWithMetadata) && propInfo.IsComplexType)
{
var existingObj = propInfo.GetValue(target);
if (existingObj != null)
{
- // ReadValue kezeli mindkét markert
+ // Marker already consumed → rewind so ReadValue can read it
+ context._position--;
var nestedValue = ReadValue(context, propInfo.PropertyType, nextDepth);
if (nestedValue != null)
{
@@ -246,25 +245,28 @@ public static partial class AcBinaryDeserializer
}
// Default: read value and set (for primitives, strings, new objects)
- var positionBeforeRead = context.Position;
+ var positionBeforeRead = context.Position - 1; // marker already consumed
try
{
- // Use typed setters for primitives and strings to avoid ReadValue dispatch
+ // Use typed setters for primitives and strings to avoid ReadValue dispatch.
+ // typeCode is already consumed — TryReadAndSetTypedValue skips its internal ReadByte.
if (propInfo.AccessorType != PropertyAccessorType.Object &&
- TryReadAndSetTypedValue(context, target, propInfo, peekCode))
+ TryReadAndSetTypedValue(context, target, propInfo, typeCode))
return;
// Complex property with Object marker: use pre-cached wrapper to skip GetWrapper lookup
var complexIdx = propInfo.ComplexPropertyIndex;
- if (complexIdx >= 0 && peekCode == BinaryTypeCode.Object)
+ if (complexIdx >= 0 && typeCode == BinaryTypeCode.Object)
{
- context.ReadByte(); // consume Object marker
+ // Marker already consumed — go straight to ReadObjectCoreWithWrapper
var propWrapper = ResolvePropertyWrapper(state.ParentWrapper, complexIdx, propInfo.PropertyType, context);
var value = ReadObjectCoreWithWrapper(context, propWrapper, nextDepth, cacheIndex: -1);
propInfo.SetValue(target, value);
}
else
{
+ // Marker already consumed → rewind so ReadValue can read it
+ context._position--;
var value = ReadValue(context, propInfo.PropertyType, nextDepth);
propInfo.SetValue(target, value);
}
@@ -275,7 +277,7 @@ public static partial class AcBinaryDeserializer
throw new AcBinaryDeserializationException(
$"Type mismatch for property '{propInfo.Name}' (index {propertyIndex}) on '{targetType.Name}'. " +
$"Expected type: '{propInfo.PropertyType.FullName}'. " +
- $"PeekCode before read: {peekCode} (0x{peekCode:X2}). " +
+ $"TypeCode read: {typeCode} (0x{typeCode:X2}). " +
$"Position before read: {positionBeforeRead}, current: {context.Position}. " +
$"Depth: {state.Depth}. " +
$"Target type: {targetType.FullName}, Assembly: {targetType.Assembly.GetName().Name} v{targetType.Assembly.GetName().Version}. " +
diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs
index 3dc462b..9f0c8fe 100644
--- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs
+++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.cs
@@ -608,194 +608,173 @@ public static partial class AcBinaryDeserializer
///
/// Tries to read and set a primitive value directly using typed setters to avoid boxing.
+ /// The type code marker byte is already consumed by the caller.
/// Returns true if handled, false if should fall back to generic path.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool TryReadAndSetTypedValue(BinaryDeserializationContext context, object target, BinaryPropertySetterBase propInfo, byte peekCode)
+ private static bool TryReadAndSetTypedValue(BinaryDeserializationContext context, object target, BinaryPropertySetterBase propInfo, byte typeCode)
where TInput : struct, IBinaryInputBase
{
// Only handle if we have a typed setter
if (propInfo.AccessorType == PropertyAccessorType.Object)
return false;
- // Handle based on property setter type and incoming data type
+ // Handle based on property setter type and incoming data type.
+ // The marker byte (typeCode) is already consumed — no ReadByte() needed.
switch (propInfo.AccessorType)
{
case PropertyAccessorType.Int32:
- if (BinaryTypeCode.IsTinyInt(peekCode))
+ if (BinaryTypeCode.IsTinyInt(typeCode))
{
- context.ReadByte();
- propInfo.SetInt32(target, BinaryTypeCode.DecodeTinyInt(peekCode));
+ propInfo.SetInt32(target, BinaryTypeCode.DecodeTinyInt(typeCode));
return true;
}
- if (peekCode == BinaryTypeCode.Int32)
+ if (typeCode == BinaryTypeCode.Int32)
{
- context.ReadByte();
propInfo.SetInt32(target, context.ReadVarInt());
return true;
}
break;
case PropertyAccessorType.Int64:
- if (BinaryTypeCode.IsTinyInt(peekCode))
+ if (BinaryTypeCode.IsTinyInt(typeCode))
{
- context.ReadByte();
- propInfo.SetInt64(target, BinaryTypeCode.DecodeTinyInt(peekCode));
+ propInfo.SetInt64(target, BinaryTypeCode.DecodeTinyInt(typeCode));
return true;
}
- if (peekCode == BinaryTypeCode.Int32)
+ if (typeCode == BinaryTypeCode.Int32)
{
- context.ReadByte();
propInfo.SetInt64(target, context.ReadVarInt());
return true;
}
- if (peekCode == BinaryTypeCode.Int64)
+ if (typeCode == BinaryTypeCode.Int64)
{
- context.ReadByte();
propInfo.SetInt64(target, context.ReadVarLong());
return true;
}
break;
case PropertyAccessorType.Boolean:
- if (peekCode == BinaryTypeCode.True)
+ if (typeCode == BinaryTypeCode.True)
{
- context.ReadByte();
propInfo.SetBoolean(target, true);
return true;
}
- if (peekCode == BinaryTypeCode.False)
+ if (typeCode == BinaryTypeCode.False)
{
- context.ReadByte();
propInfo.SetBoolean(target, false);
return true;
}
break;
case PropertyAccessorType.Double:
- if (peekCode == BinaryTypeCode.Float64)
+ if (typeCode == BinaryTypeCode.Float64)
{
- context.ReadByte();
propInfo.SetDouble(target, context.ReadDoubleUnsafe());
return true;
}
break;
case PropertyAccessorType.Single:
- if (peekCode == BinaryTypeCode.Float32)
+ if (typeCode == BinaryTypeCode.Float32)
{
- context.ReadByte();
propInfo.SetSingle(target, context.ReadSingleUnsafe());
return true;
}
break;
case PropertyAccessorType.Decimal:
- if (peekCode == BinaryTypeCode.Decimal)
+ if (typeCode == BinaryTypeCode.Decimal)
{
- context.ReadByte();
propInfo.SetDecimal(target, context.ReadDecimalUnsafe());
return true;
}
break;
case PropertyAccessorType.DateTime:
- if (peekCode == BinaryTypeCode.DateTime)
+ if (typeCode == BinaryTypeCode.DateTime)
{
- context.ReadByte();
propInfo.SetDateTime(target, context.ReadDateTimeUnsafe());
return true;
}
break;
case PropertyAccessorType.Guid:
- if (peekCode == BinaryTypeCode.Guid)
+ if (typeCode == BinaryTypeCode.Guid)
{
- context.ReadByte();
propInfo.SetGuid(target, context.ReadGuidUnsafe());
return true;
}
break;
case PropertyAccessorType.Byte:
- if (BinaryTypeCode.IsTinyInt(peekCode))
+ if (BinaryTypeCode.IsTinyInt(typeCode))
{
- context.ReadByte();
- propInfo.SetByte(target, (byte)BinaryTypeCode.DecodeTinyInt(peekCode));
+ propInfo.SetByte(target, (byte)BinaryTypeCode.DecodeTinyInt(typeCode));
return true;
}
- if (peekCode == BinaryTypeCode.UInt8)
+ if (typeCode == BinaryTypeCode.UInt8)
{
- context.ReadByte();
propInfo.SetByte(target, context.ReadByte());
return true;
}
break;
case PropertyAccessorType.Int16:
- if (BinaryTypeCode.IsTinyInt(peekCode))
+ if (BinaryTypeCode.IsTinyInt(typeCode))
{
- context.ReadByte();
- propInfo.SetInt16(target, (short)BinaryTypeCode.DecodeTinyInt(peekCode));
+ propInfo.SetInt16(target, (short)BinaryTypeCode.DecodeTinyInt(typeCode));
return true;
}
- if (peekCode == BinaryTypeCode.Int16)
+ if (typeCode == BinaryTypeCode.Int16)
{
- context.ReadByte();
propInfo.SetInt16(target, context.ReadInt16Unsafe());
return true;
}
break;
case PropertyAccessorType.UInt16:
- if (BinaryTypeCode.IsTinyInt(peekCode))
+ if (BinaryTypeCode.IsTinyInt(typeCode))
{
- context.ReadByte();
- propInfo.SetUInt16(target, (ushort)BinaryTypeCode.DecodeTinyInt(peekCode));
+ propInfo.SetUInt16(target, (ushort)BinaryTypeCode.DecodeTinyInt(typeCode));
return true;
}
- if (peekCode == BinaryTypeCode.UInt16)
+ if (typeCode == BinaryTypeCode.UInt16)
{
- context.ReadByte();
propInfo.SetUInt16(target, context.ReadUInt16Unsafe());
return true;
}
break;
case PropertyAccessorType.UInt32:
- if (BinaryTypeCode.IsTinyInt(peekCode))
+ if (BinaryTypeCode.IsTinyInt(typeCode))
{
- context.ReadByte();
- propInfo.SetUInt32(target, (uint)BinaryTypeCode.DecodeTinyInt(peekCode));
+ propInfo.SetUInt32(target, (uint)BinaryTypeCode.DecodeTinyInt(typeCode));
return true;
}
- if (peekCode == BinaryTypeCode.UInt32)
+ if (typeCode == BinaryTypeCode.UInt32)
{
- context.ReadByte();
propInfo.SetUInt32(target, context.ReadVarUInt());
return true;
}
break;
case PropertyAccessorType.UInt64:
- if (BinaryTypeCode.IsTinyInt(peekCode))
+ if (BinaryTypeCode.IsTinyInt(typeCode))
{
- context.ReadByte();
- propInfo.SetUInt64(target, (ulong)BinaryTypeCode.DecodeTinyInt(peekCode));
+ propInfo.SetUInt64(target, (ulong)BinaryTypeCode.DecodeTinyInt(typeCode));
return true;
}
- if (peekCode == BinaryTypeCode.UInt64)
+ if (typeCode == BinaryTypeCode.UInt64)
{
- context.ReadByte();
propInfo.SetUInt64(target, context.ReadVarULong());
return true;
}
break;
case PropertyAccessorType.Enum:
- if (peekCode == BinaryTypeCode.Enum)
+ if (typeCode == BinaryTypeCode.Enum)
{
- context.ReadByte();
var enumByte = context.ReadByte();
int enumValue;
if (BinaryTypeCode.IsTinyInt(enumByte))
@@ -808,43 +787,37 @@ public static partial class AcBinaryDeserializer
return true;
}
// Enum can also be encoded as TinyInt directly
- if (BinaryTypeCode.IsTinyInt(peekCode))
+ if (BinaryTypeCode.IsTinyInt(typeCode))
{
- context.ReadByte();
- propInfo.SetEnumAsInt32(target, BinaryTypeCode.DecodeTinyInt(peekCode));
+ propInfo.SetEnumAsInt32(target, BinaryTypeCode.DecodeTinyInt(typeCode));
return true;
}
break;
case PropertyAccessorType.String:
- if (BinaryTypeCode.IsFixStr(peekCode))
+ if (BinaryTypeCode.IsFixStr(typeCode))
{
- context.ReadByte();
- var length = BinaryTypeCode.DecodeFixStrLength(peekCode);
+ var length = BinaryTypeCode.DecodeFixStrLength(typeCode);
propInfo.SetValue(target, length == 0 ? string.Empty : context.ReadStringUtf8(length));
return true;
}
- if (peekCode == BinaryTypeCode.String)
+ if (typeCode == BinaryTypeCode.String)
{
- context.ReadByte();
propInfo.SetValue(target, ReadPlainString(context));
return true;
}
- if (peekCode == BinaryTypeCode.StringEmpty)
+ if (typeCode == BinaryTypeCode.StringEmpty)
{
- context.ReadByte();
propInfo.SetValue(target, string.Empty);
return true;
}
- if (peekCode == BinaryTypeCode.StringInterned)
+ if (typeCode == BinaryTypeCode.StringInterned)
{
- context.ReadByte();
propInfo.SetValue(target, context.GetInternedString((int)context.ReadVarUInt()));
return true;
}
- if (peekCode == BinaryTypeCode.StringInternFirst)
+ if (typeCode == BinaryTypeCode.StringInternFirst)
{
- context.ReadByte();
propInfo.SetValue(target, ReadAndRegisterInternedString(context));
return true;
}
diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs
index f1f0a27..ca66ad8 100644
--- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs
+++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs
@@ -133,6 +133,7 @@ public static partial class AcBinarySerializer
///
public bool HasCaching => UseStringInterning || ReferenceHandling != ReferenceHandlingMode.None;
public bool UseMetadata => Options.UseMetadata;
+ public bool UseGeneratedCode => Options.UseGeneratedCode;
public byte MinStringInternLength => Options.MinStringInternLength;
public byte MaxStringInternLength => Options.MaxStringInternLength;
public BinaryPropertyFilter? PropertyFilter => Options.PropertyFilter;
diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs
index 803a1b2..1429b62 100644
--- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs
+++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs
@@ -1109,13 +1109,16 @@ public static partial class AcBinarySerializer
// Source-generated fast path: bypass the entire switch/delegate loop.
// Only when no caching features are active (no string interning, no reference handling)
// to avoid scan pass / write pass mismatch with interned strings and tracked references.
- var generatedWriter = wrapper.GeneratedWriter;
- if (generatedWriter != null && !hasPropertyFilter && !context.UseMetadata && !context.HasCaching)
+ if (context.UseGeneratedCode)
{
- generatedWriter.WriteProperties(value, context, nextDepth);
- return;
+ var generatedWriter = wrapper.GeneratedWriter;
+ if (generatedWriter != null && !hasPropertyFilter && !context.UseMetadata && !context.HasCaching)
+ {
+ generatedWriter.WriteProperties(value, context, nextDepth);
+ return;
+ }
}
-
+
if (!context.UseMetadata)
{
// Markerless loop: no extra branching per property for the common case.
diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs
index f52489b..a41a6df 100644
--- a/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs
+++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs
@@ -84,6 +84,8 @@ public sealed class AcBinarySerializerOptions : AcSerializerOptions
///
public bool UseMetadata { get; set; } = false;
+ public bool UseGeneratedCode { get; set; } = true;
+
///
/// When true, checks for duplicate property name hashes during serialization (UseMetadata mode).
/// Throws exception if FNV-1a hash collision is detected between property names of the same type.