Add fast-path support for more primitive collections
Extend serializer/deserializer to efficiently handle arrays and lists of short, ushort, uint, ulong, sbyte, char, DateTimeOffset, and TimeSpan. Improve enum collection handling and support ICollection-based collections. Update source generator to distinguish between indexable and counted collections, generating optimal serialization code for each. These changes enhance performance and coverage for a broader range of collection types.
This commit is contained in:
parent
7977feb36a
commit
b244d9219a
|
|
@ -128,12 +128,26 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
elemKind = GetKind(elemType);
|
elemKind = GetKind(elemType);
|
||||||
elemFullTypeName = elemType.ToDisplayString();
|
elemFullTypeName = elemType.ToDisplayString();
|
||||||
|
|
||||||
// Detect collection shape: List<T> or T[]
|
// Detect collection shape for inline write
|
||||||
if (p.Type is IArrayTypeSymbol)
|
if (p.Type is IArrayTypeSymbol)
|
||||||
collKind = "Array";
|
collKind = "Array";
|
||||||
else if (p.Type is INamedTypeSymbol collNamedType &&
|
else if (p.Type is INamedTypeSymbol collNamedType)
|
||||||
collNamedType.OriginalDefinition.ToDisplayString() == "System.Collections.Generic.List<T>")
|
{
|
||||||
collKind = "List";
|
var origDef = collNamedType.OriginalDefinition.ToDisplayString();
|
||||||
|
collKind = origDef switch
|
||||||
|
{
|
||||||
|
"System.Collections.Generic.List<T>" => "List",
|
||||||
|
"System.Collections.Generic.IList<T>" => "List", // has Count + indexer
|
||||||
|
"System.Collections.Generic.IReadOnlyList<T>" => "List", // has Count + indexer
|
||||||
|
"System.Collections.Generic.HashSet<T>" => "Counted", // has Count, no indexer
|
||||||
|
"System.Collections.Generic.Queue<T>" => "Counted",
|
||||||
|
"System.Collections.Generic.ICollection<T>" => "Counted",
|
||||||
|
"System.Collections.Generic.IReadOnlyCollection<T>" => "Counted",
|
||||||
|
"System.Collections.Generic.SortedSet<T>" => "Counted",
|
||||||
|
"System.Collections.Generic.LinkedList<T>" => "Counted",
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// For Complex element types, check for generated writer
|
// For Complex element types, check for generated writer
|
||||||
if (elemKind == PropertyTypeKind.Complex)
|
if (elemKind == PropertyTypeKind.Complex)
|
||||||
|
|
@ -450,7 +464,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
sb.AppendLine($"{i}{{");
|
sb.AppendLine($"{i}{{");
|
||||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Array);");
|
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Array);");
|
||||||
|
|
||||||
// Get count and span/indexer based on collection kind
|
// Get count and iteration based on collection kind
|
||||||
if (p.CollectionKind == "Array")
|
if (p.CollectionKind == "Array")
|
||||||
{
|
{
|
||||||
sb.AppendLine($"{i} var arr_{p.Name} = {a};");
|
sb.AppendLine($"{i} var arr_{p.Name} = {a};");
|
||||||
|
|
@ -460,7 +474,16 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
sb.AppendLine($"{i} {{");
|
sb.AppendLine($"{i} {{");
|
||||||
sb.AppendLine($"{i} var elem_{p.Name} = arr_{p.Name}[i_{p.Name}];");
|
sb.AppendLine($"{i} var elem_{p.Name} = arr_{p.Name}[i_{p.Name}];");
|
||||||
}
|
}
|
||||||
else // List
|
else if (p.CollectionKind == "Counted")
|
||||||
|
{
|
||||||
|
// HashSet<T>, Queue<T>, ICollection<T>, IReadOnlyCollection<T>, etc. — Count + foreach
|
||||||
|
sb.AppendLine($"{i} var col_{p.Name} = {a};");
|
||||||
|
sb.AppendLine($"{i} context.WriteVarUInt((uint)col_{p.Name}.Count);");
|
||||||
|
sb.AppendLine($"{i} var nextDepth_{p.Name} = depth + 1;");
|
||||||
|
sb.AppendLine($"{i} foreach (var elem_{p.Name} in col_{p.Name})");
|
||||||
|
sb.AppendLine($"{i} {{");
|
||||||
|
}
|
||||||
|
else // List, IList<T>, IReadOnlyList<T> — Count + indexer
|
||||||
{
|
{
|
||||||
sb.AppendLine($"{i} var list_{p.Name} = {a};");
|
sb.AppendLine($"{i} var list_{p.Name} = {a};");
|
||||||
sb.AppendLine($"{i} context.WriteVarUInt((uint)list_{p.Name}.Count);");
|
sb.AppendLine($"{i} context.WriteVarUInt((uint)list_{p.Name}.Count);");
|
||||||
|
|
|
||||||
|
|
@ -1458,6 +1458,132 @@ public static partial class AcBinaryDeserializer
|
||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Float array
|
||||||
|
if (ReferenceEquals(elementType, FloatType))
|
||||||
|
{
|
||||||
|
var array = new float[count];
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
var typeCode = context.ReadByte();
|
||||||
|
if (typeCode != BinaryTypeCode.Float32) return null;
|
||||||
|
array[i] = context.ReadSingleUnsafe();
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Short (Int16) array
|
||||||
|
if (ReferenceEquals(elementType, ShortType))
|
||||||
|
{
|
||||||
|
var array = new short[count];
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
var typeCode = context.ReadByte();
|
||||||
|
if (typeCode != BinaryTypeCode.Int16) return null;
|
||||||
|
array[i] = context.ReadInt16Unsafe();
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UShort (UInt16) array
|
||||||
|
if (ReferenceEquals(elementType, UShortType))
|
||||||
|
{
|
||||||
|
var array = new ushort[count];
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
var typeCode = context.ReadByte();
|
||||||
|
if (typeCode != BinaryTypeCode.UInt16) return null;
|
||||||
|
array[i] = context.ReadUInt16Unsafe();
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UInt32 array
|
||||||
|
if (ReferenceEquals(elementType, UIntType))
|
||||||
|
{
|
||||||
|
var array = new uint[count];
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
var typeCode = context.ReadByte();
|
||||||
|
if (typeCode != BinaryTypeCode.UInt32) return null;
|
||||||
|
array[i] = context.ReadVarUInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UInt64 array
|
||||||
|
if (ReferenceEquals(elementType, ULongType))
|
||||||
|
{
|
||||||
|
var array = new ulong[count];
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
var typeCode = context.ReadByte();
|
||||||
|
if (typeCode != BinaryTypeCode.UInt64) return null;
|
||||||
|
array[i] = context.ReadVarULong();
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SByte (Int8) array
|
||||||
|
if (ReferenceEquals(elementType, SByteType))
|
||||||
|
{
|
||||||
|
var array = new sbyte[count];
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
var typeCode = context.ReadByte();
|
||||||
|
if (typeCode != BinaryTypeCode.Int8) return null;
|
||||||
|
array[i] = unchecked((sbyte)context.ReadByte());
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Char array
|
||||||
|
if (ReferenceEquals(elementType, CharType))
|
||||||
|
{
|
||||||
|
var array = new char[count];
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
var typeCode = context.ReadByte();
|
||||||
|
if (typeCode != BinaryTypeCode.Char) return null;
|
||||||
|
array[i] = context.ReadCharUnsafe();
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DateTimeOffset array
|
||||||
|
if (ReferenceEquals(elementType, DateTimeOffsetType))
|
||||||
|
{
|
||||||
|
var array = new DateTimeOffset[count];
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
var typeCode = context.ReadByte();
|
||||||
|
if (typeCode != BinaryTypeCode.DateTimeOffset) return null;
|
||||||
|
array[i] = context.ReadDateTimeOffsetUnsafe();
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimeSpan array
|
||||||
|
if (ReferenceEquals(elementType, TimeSpanType))
|
||||||
|
{
|
||||||
|
var array = new TimeSpan[count];
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
var typeCode = context.ReadByte();
|
||||||
|
if (typeCode != BinaryTypeCode.TimeSpan) return null;
|
||||||
|
array[i] = context.ReadTimeSpanUnsafe();
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,13 @@ public static partial class AcBinarySerializer
|
||||||
private static readonly Type DecimalType = typeof(decimal);
|
private static readonly Type DecimalType = typeof(decimal);
|
||||||
private static readonly Type BoolType = typeof(bool);
|
private static readonly Type BoolType = typeof(bool);
|
||||||
private static readonly Type DateTimeType = typeof(DateTime);
|
private static readonly Type DateTimeType = typeof(DateTime);
|
||||||
|
private static readonly Type ShortType = typeof(short);
|
||||||
|
private static readonly Type UShortType = typeof(ushort);
|
||||||
|
private static readonly Type UIntType = typeof(uint);
|
||||||
|
private static readonly Type ULongType = typeof(ulong);
|
||||||
|
private static readonly Type ByteType = typeof(byte);
|
||||||
|
private static readonly Type SByteType = typeof(sbyte);
|
||||||
|
private static readonly Type CharType = typeof(char);
|
||||||
|
|
||||||
#region Public API
|
#region Public API
|
||||||
|
|
||||||
|
|
@ -1534,7 +1541,7 @@ public static partial class AcBinarySerializer
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For IList, we can write the count directly
|
// For IList, we can write the count directly and use indexed access
|
||||||
if (enumerable is IList list)
|
if (enumerable is IList list)
|
||||||
{
|
{
|
||||||
var count = list.Count;
|
var count = list.Count;
|
||||||
|
|
@ -1548,7 +1555,19 @@ public static partial class AcBinarySerializer
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For other IEnumerable, collect first
|
// For ICollection (HashSet<T>, Queue<T>, SortedSet<T>, etc.) — has Count, no indexer
|
||||||
|
if (enumerable is ICollection collection)
|
||||||
|
{
|
||||||
|
context.WriteVarUInt((uint)collection.Count);
|
||||||
|
foreach (var item in enumerable)
|
||||||
|
{
|
||||||
|
var itemType = item?.GetType() ?? typeof(object);
|
||||||
|
WriteValue(item, itemType, context, nextDepth);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For other IEnumerable (rare: custom IEnumerable without ICollection), collect first
|
||||||
var items = new List<object?>();
|
var items = new List<object?>();
|
||||||
foreach (var item in enumerable)
|
foreach (var item in enumerable)
|
||||||
{
|
{
|
||||||
|
|
@ -1689,6 +1708,158 @@ public static partial class AcBinarySerializer
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ReferenceEquals(elementType, ShortType))
|
||||||
|
{
|
||||||
|
ReadOnlySpan<short> span;
|
||||||
|
if (enumerable is short[] arr) span = arr;
|
||||||
|
else if (enumerable is List<short> list) span = CollectionsMarshal.AsSpan(list);
|
||||||
|
else return false;
|
||||||
|
|
||||||
|
context.WriteVarUInt((uint)span.Length);
|
||||||
|
for (var i = 0; i < span.Length; i++)
|
||||||
|
WriteInt16Unsafe(span[i], context);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ReferenceEquals(elementType, UShortType))
|
||||||
|
{
|
||||||
|
ReadOnlySpan<ushort> span;
|
||||||
|
if (enumerable is ushort[] arr) span = arr;
|
||||||
|
else if (enumerable is List<ushort> list) span = CollectionsMarshal.AsSpan(list);
|
||||||
|
else return false;
|
||||||
|
|
||||||
|
context.WriteVarUInt((uint)span.Length);
|
||||||
|
for (var i = 0; i < span.Length; i++)
|
||||||
|
WriteUInt16Unsafe(span[i], context);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ReferenceEquals(elementType, UIntType))
|
||||||
|
{
|
||||||
|
ReadOnlySpan<uint> span;
|
||||||
|
if (enumerable is uint[] arr) span = arr;
|
||||||
|
else if (enumerable is List<uint> list) span = CollectionsMarshal.AsSpan(list);
|
||||||
|
else return false;
|
||||||
|
|
||||||
|
context.WriteVarUInt((uint)span.Length);
|
||||||
|
for (var i = 0; i < span.Length; i++)
|
||||||
|
WriteUInt32(span[i], context);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ReferenceEquals(elementType, ULongType))
|
||||||
|
{
|
||||||
|
ReadOnlySpan<ulong> span;
|
||||||
|
if (enumerable is ulong[] arr) span = arr;
|
||||||
|
else if (enumerable is List<ulong> list) span = CollectionsMarshal.AsSpan(list);
|
||||||
|
else return false;
|
||||||
|
|
||||||
|
context.WriteVarUInt((uint)span.Length);
|
||||||
|
for (var i = 0; i < span.Length; i++)
|
||||||
|
WriteUInt64(span[i], context);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ReferenceEquals(elementType, ByteType))
|
||||||
|
{
|
||||||
|
// Note: top-level byte[] goes through WriteByteArray (BinaryTypeCode.ByteArray).
|
||||||
|
// This handles List<byte> and byte[] when reached via WriteArray (element-level encoding).
|
||||||
|
ReadOnlySpan<byte> span;
|
||||||
|
if (enumerable is byte[] arr) span = arr;
|
||||||
|
else if (enumerable is List<byte> list) span = CollectionsMarshal.AsSpan(list);
|
||||||
|
else return false;
|
||||||
|
|
||||||
|
context.WriteVarUInt((uint)span.Length);
|
||||||
|
for (var i = 0; i < span.Length; i++)
|
||||||
|
{
|
||||||
|
context.WriteByte(BinaryTypeCode.UInt8);
|
||||||
|
context.WriteByte(span[i]);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ReferenceEquals(elementType, SByteType))
|
||||||
|
{
|
||||||
|
ReadOnlySpan<sbyte> span;
|
||||||
|
if (enumerable is sbyte[] arr) span = arr;
|
||||||
|
else if (enumerable is List<sbyte> list) span = CollectionsMarshal.AsSpan(list);
|
||||||
|
else return false;
|
||||||
|
|
||||||
|
context.WriteVarUInt((uint)span.Length);
|
||||||
|
for (var i = 0; i < span.Length; i++)
|
||||||
|
{
|
||||||
|
context.WriteByte(BinaryTypeCode.Int8);
|
||||||
|
context.WriteByte(unchecked((byte)span[i]));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ReferenceEquals(elementType, CharType))
|
||||||
|
{
|
||||||
|
ReadOnlySpan<char> span;
|
||||||
|
if (enumerable is char[] arr) span = arr;
|
||||||
|
else if (enumerable is List<char> list) span = CollectionsMarshal.AsSpan(list);
|
||||||
|
else return false;
|
||||||
|
|
||||||
|
context.WriteVarUInt((uint)span.Length);
|
||||||
|
for (var i = 0; i < span.Length; i++)
|
||||||
|
WriteCharUnsafe(span[i], context);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ReferenceEquals(elementType, DateTimeOffsetType))
|
||||||
|
{
|
||||||
|
ReadOnlySpan<DateTimeOffset> span;
|
||||||
|
if (enumerable is DateTimeOffset[] arr) span = arr;
|
||||||
|
else if (enumerable is List<DateTimeOffset> list) span = CollectionsMarshal.AsSpan(list);
|
||||||
|
else return false;
|
||||||
|
|
||||||
|
context.WriteVarUInt((uint)span.Length);
|
||||||
|
for (var i = 0; i < span.Length; i++)
|
||||||
|
WriteDateTimeOffsetUnsafe(span[i], context);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ReferenceEquals(elementType, TimeSpanType))
|
||||||
|
{
|
||||||
|
ReadOnlySpan<TimeSpan> span;
|
||||||
|
if (enumerable is TimeSpan[] arr) span = arr;
|
||||||
|
else if (enumerable is List<TimeSpan> list) span = CollectionsMarshal.AsSpan(list);
|
||||||
|
else return false;
|
||||||
|
|
||||||
|
context.WriteVarUInt((uint)span.Length);
|
||||||
|
for (var i = 0; i < span.Length; i++)
|
||||||
|
WriteTimeSpanUnsafe(span[i], context);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enum collections: iterate without GetWrapper/WriteValue dispatch
|
||||||
|
if (elementType.IsEnum)
|
||||||
|
{
|
||||||
|
if (enumerable is IList enumList)
|
||||||
|
{
|
||||||
|
context.WriteVarUInt((uint)enumList.Count);
|
||||||
|
for (var i = 0; i < enumList.Count; i++)
|
||||||
|
WriteEnum(enumList[i]!, context);
|
||||||
|
}
|
||||||
|
else if (enumerable is ICollection enumCol)
|
||||||
|
{
|
||||||
|
context.WriteVarUInt((uint)enumCol.Count);
|
||||||
|
foreach (var item in enumerable)
|
||||||
|
WriteEnum(item!, context);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var items = new List<object>();
|
||||||
|
foreach (var item in enumerable)
|
||||||
|
items.Add(item!);
|
||||||
|
context.WriteVarUInt((uint)items.Count);
|
||||||
|
for (var i = 0; i < items.Count; i++)
|
||||||
|
WriteEnum(items[i], context);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue