diff --git a/AyCode.Core/Extensions/SerializeObjectExtensions.cs b/AyCode.Core/Extensions/SerializeObjectExtensions.cs
index b6648a7..461d71c 100644
--- a/AyCode.Core/Extensions/SerializeObjectExtensions.cs
+++ b/AyCode.Core/Extensions/SerializeObjectExtensions.cs
@@ -681,31 +681,31 @@ public static class SerializeObjectExtensions
#region Clone and Copy (Binary-based, zero intermediate allocation)
///
- /// Clone object via binary serialization (zero intermediate byte[] allocation).
- /// Uses ArrayBufferWriter to serialize directly into a buffer, then deserializes from the span.
+ /// Clone object via binary serialization. Uses the byte[]-output overload (pool-context internally,
+ /// 1 final allocation) — lighter than the IBufferWriter path (2 allocations: ArrayBufferWriter + buffer).
///
public static TDestination? CloneTo(this object? src) where TDestination : class
{
if (src == null) return null;
- var buffer = new ArrayBufferWriter(256);
- AcBinarySerializer.Serialize(src, buffer, AcBinarySerializerOptions.Default);
- MemoryMarshal.TryGetArray(buffer.WrittenMemory, out var seg);
- return AcBinaryDeserializer.Deserialize(seg.Array!, seg.Offset, seg.Count);
+ // Pass explicit runtime type — src is statically object?, so the generic Serialize overload
+ // would infer T = object and emit an object-typed wire payload, losing all concrete DTO properties.
+ var bytes = AcBinarySerializer.Serialize(src, src.GetType(), AcBinarySerializerOptions.Default);
+ return AcBinaryDeserializer.Deserialize(bytes);
}
///
- /// Copy object properties to target via binary serialization (zero intermediate byte[] allocation).
- /// Uses ArrayBufferWriter to serialize directly into a buffer, then populates target from the backing array.
+ /// Copy object properties to target via binary serialization. Uses the byte[]-output overload
+ /// (pool-context internally, 1 final allocation) — lighter than the IBufferWriter path.
///
public static void CopyTo(this object? src, object target)
{
if (src == null) return;
- var buffer = new ArrayBufferWriter(256);
- AcBinarySerializer.Serialize(src, buffer, AcBinarySerializerOptions.Default);
- MemoryMarshal.TryGetArray(buffer.WrittenMemory, out var seg);
- AcBinaryDeserializer.Populate(seg.Array!, seg.Offset, seg.Count, target);
+ // Pass explicit runtime type — src is statically object?, so the generic Serialize overload
+ // would infer T = object and emit an object-typed wire payload, losing all concrete DTO properties.
+ var bytes = AcBinarySerializer.Serialize(src, src.GetType(), AcBinarySerializerOptions.Default);
+ AcBinaryDeserializer.Populate(bytes, 0, bytes.Length, target);
}
#endregion
diff --git a/AyCode.Services/SignalRs/AcBinaryHubProtocol.cs b/AyCode.Services/SignalRs/AcBinaryHubProtocol.cs
index 6f24439..9eec93c 100644
--- a/AyCode.Services/SignalRs/AcBinaryHubProtocol.cs
+++ b/AyCode.Services/SignalRs/AcBinaryHubProtocol.cs
@@ -1379,6 +1379,13 @@ public class AcBinaryHubProtocol : IHubProtocol
Console.WriteLine($"[DEBUG] WriteArgument runtimeType={runtimeType.FullName} argBytes={argBytes} valueIsNull={value == null} kind={kind}");
}
+ [Conditional("DEBUG")]
+ protected void DebugLogArgument(Type targetType, int argLength, long remaining)
+ {
+ _logger?.LogDebug("ReadSingleArgument targetType={TargetType} argLength={ArgLength} remaining={Remaining}", targetType.FullName, argLength, remaining);
+ Console.WriteLine($"[DEBUG] ReadSingleArgument targetType={targetType.FullName} argLength={argLength} remaining={remaining}");
+ }
+
private object?[] ReadArguments(ref SequenceReader r, IReadOnlyList paramTypes, object? headerContext)
{
var count = (int)ReadVarUInt(ref r);
diff --git a/AyCode.Services/SignalRs/ISignalParams.cs b/AyCode.Services/SignalRs/ISignalParams.cs
index 1a80670..e845022 100644
--- a/AyCode.Services/SignalRs/ISignalParams.cs
+++ b/AyCode.Services/SignalRs/ISignalParams.cs
@@ -64,9 +64,10 @@ public class SignalParams : ISignalParams
{
// Pass explicit runtime type — parameters[i] is statically object, so the generic
// ToBinary() overload would infer T = object and emit an object-typed wire payload
- // (instead of the concrete int/string/DTO type). The server's GetParameterValues then
- // deserializes to default(targetType) → 0 for int, null for reference types, etc.
- paramBytes[i] = parameters[i].ToBinary(parameters[i]?.GetType() ?? typeof(object));
+ // (instead of the concrete int/string/DTO type). Explicit null-handling avoids the
+ // null-conditional + fallback indirection and produces the Null marker directly.
+ var p = parameters[i];
+ paramBytes[i] = p == null ? [BinaryTypeCode.Null] : p.ToBinary(p.GetType());
}
_parameterValues = paramBytes;
@@ -93,9 +94,7 @@ public class SignalParams : ISignalParams
return null;
// Deserialize byte[] → byte[][] (cached)
- _parameterValues ??= Parameters is { Length: > 0 }
- ? Parameters.BinaryTo()
- : null;
+ _parameterValues ??= Parameters is { Length: > 0 } ? Parameters.BinaryTo() : null;
if (_parameterValues is null or { Length: 0 })
return null;
diff --git a/AyCode.Services/SignalRs/SignalRSerializationHelper.cs b/AyCode.Services/SignalRs/SignalRSerializationHelper.cs
index 7fd8866..51c8e8e 100644
--- a/AyCode.Services/SignalRs/SignalRSerializationHelper.cs
+++ b/AyCode.Services/SignalRs/SignalRSerializationHelper.cs
@@ -62,18 +62,16 @@ public static class SignalRSerializationHelper
#region Binary Serialization
///
- /// Serialize object to binary using pooled ArrayBufferWriter.
+ /// Serialize object to binary via the pool-backed byte[]-output overload (1 final allocation).
+ /// Lighter than the IBufferWriter path (2 allocations: ArrayBufferWriter + buffer + ToArray copy).
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte[] SerializeToBinary(T value, AcBinarySerializerOptions? options = null)
- {
- var writer = new ArrayBufferWriter(256);
- value.ToBinary(writer, options ?? AcBinarySerializerOptions.Default);
- return writer.WrittenSpan.ToArray();
- }
+ => value.ToBinary(options ?? AcBinarySerializerOptions.Default);
///
- /// Serialize object to binary and write to existing ArrayBufferWriter.
+ /// Serialize object to binary and write to existing ArrayBufferWriter. Caller owns the writer
+ /// — useful for buffer-reuse scenarios where the same writer accepts multiple writes.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SerializeToBinary(T value, ArrayBufferWriter writer, AcBinarySerializerOptions? options = null)
@@ -89,11 +87,7 @@ public static class SignalRSerializationHelper
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte[] SerializeToBinary(object? value, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type, AcBinarySerializerOptions? options = null)
- {
- var writer = new ArrayBufferWriter(256);
- value.ToBinary(type, writer, options ?? AcBinarySerializerOptions.Default);
- return writer.WrittenSpan.ToArray();
- }
+ => value.ToBinary(type, options ?? AcBinarySerializerOptions.Default);
///
/// Deserialize binary data to object.