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);
|
||||
elemFullTypeName = elemType.ToDisplayString();
|
||||
|
||||
// Detect collection shape: List<T> or T[]
|
||||
// Detect collection shape for inline write
|
||||
if (p.Type is IArrayTypeSymbol)
|
||||
collKind = "Array";
|
||||
else if (p.Type is INamedTypeSymbol collNamedType &&
|
||||
collNamedType.OriginalDefinition.ToDisplayString() == "System.Collections.Generic.List<T>")
|
||||
collKind = "List";
|
||||
else if (p.Type is INamedTypeSymbol collNamedType)
|
||||
{
|
||||
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
|
||||
if (elemKind == PropertyTypeKind.Complex)
|
||||
|
|
@ -450,7 +464,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
sb.AppendLine($"{i}{{");
|
||||
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")
|
||||
{
|
||||
sb.AppendLine($"{i} var arr_{p.Name} = {a};");
|
||||
|
|
@ -460,7 +474,16 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
|||
sb.AppendLine($"{i} {{");
|
||||
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} context.WriteVarUInt((uint)list_{p.Name}.Count);");
|
||||
|
|
|
|||
|
|
@ -1458,6 +1458,132 @@ public static partial class AcBinaryDeserializer
|
|||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,13 @@ public static partial class AcBinarySerializer
|
|||
private static readonly Type DecimalType = typeof(decimal);
|
||||
private static readonly Type BoolType = typeof(bool);
|
||||
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
|
||||
|
||||
|
|
@ -1534,7 +1541,7 @@ public static partial class AcBinarySerializer
|
|||
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)
|
||||
{
|
||||
var count = list.Count;
|
||||
|
|
@ -1548,7 +1555,19 @@ public static partial class AcBinarySerializer
|
|||
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?>();
|
||||
foreach (var item in enumerable)
|
||||
{
|
||||
|
|
@ -1689,6 +1708,158 @@ public static partial class AcBinarySerializer
|
|||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue