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.