Refactor list factory to support capacity for deserialization

Update list factory cache and GetOrCreateListFactory to accept a capacity parameter, enabling preallocation of lists during deserialization. Adjust deserializer code to pass collection size where available, improving performance and memory usage. Also, pre-size dictionaries on creation and add Bash(find:*) to allowed commands in settings.local.json.
This commit is contained in:
Loretta 2026-02-02 19:02:17 +01:00
parent bc62488965
commit b7cb6256a0
5 changed files with 28 additions and 12 deletions

View File

@ -28,7 +28,8 @@
"Bash(timeout 30 dotnet run:*)",
"Bash(dotnet exec vstest:*)",
"Bash(dotnet new:*)",
"Bash(Remove-Item \"H:\\\\Applications\\\\Aycode\\\\Source\\\\AyCode.Core\\\\AyCode.Core\\\\Serializers\\\\Toons\\\\AcToonSerializer.RelationshipDetection.cs\")"
"Bash(Remove-Item \"H:\\\\Applications\\\\Aycode\\\\Source\\\\AyCode.Core\\\\AyCode.Core\\\\Serializers\\\\Toons\\\\AcToonSerializer.RelationshipDetection.cs\")",
"Bash(find:*)"
]
}
}

View File

@ -97,7 +97,7 @@ public static class JsonUtilities
private static readonly ConcurrentDictionary<Type, bool> IsCollectionCache = new();
private static readonly ConcurrentDictionary<Type, bool> IsPrimitiveCollectionCache = new();
private static readonly ConcurrentDictionary<PropertyInfo, bool> JsonIgnoreCache = new();
private static readonly ConcurrentDictionary<Type, Func<IList>> ListFactoryCache = new();
private static readonly ConcurrentDictionary<Type, Func<int, IList>> ListFactoryCache = new();
#endregion
@ -585,16 +585,32 @@ public static class JsonUtilities
/// <summary>
/// Gets or creates a list factory for a given element type.
/// Accepts capacity parameter: capacity &lt;= 0 uses parameterless constructor,
/// capacity &gt; 0 uses List&lt;T&gt;(capacity) constructor.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Func<IList> GetOrCreateListFactory(in Type elementType)
public static Func<int, IList> GetOrCreateListFactory(in Type elementType)
{
return ListFactoryCache.GetOrAdd(elementType, static t =>
{
var listType = ListGenericType.MakeGenericType(t);
var newExpr = System.Linq.Expressions.Expression.New(listType);
var castExpr = System.Linq.Expressions.Expression.Convert(newExpr, typeof(IList));
return System.Linq.Expressions.Expression.Lambda<Func<IList>>(castExpr).Compile();
var capacityParam = System.Linq.Expressions.Expression.Parameter(typeof(int), "capacity");
// new List<T>() - parameterless
var defaultCtor = listType.GetConstructor(Type.EmptyTypes)!;
var newDefault = System.Linq.Expressions.Expression.New(defaultCtor);
// new List<T>(capacity) - with capacity
var capacityCtor = listType.GetConstructor(new[] { typeof(int) })!;
var newWithCapacity = System.Linq.Expressions.Expression.New(capacityCtor, capacityParam);
// capacity > 0 ? new List<T>(capacity) : new List<T>()
var condition = System.Linq.Expressions.Expression.Condition(
System.Linq.Expressions.Expression.GreaterThan(capacityParam, System.Linq.Expressions.Expression.Constant(0)),
System.Linq.Expressions.Expression.Convert(newWithCapacity, typeof(IList)),
System.Linq.Expressions.Expression.Convert(newDefault, typeof(IList)));
return System.Linq.Expressions.Expression.Lambda<Func<int, IList>>(condition, capacityParam).Compile();
});
}

View File

@ -1032,7 +1032,7 @@ public static partial class AcBinaryDeserializer
/* Fallback to List<T> */
}
list ??= GetOrCreateListFactory(elementType)();
list ??= GetOrCreateListFactory(elementType)(count);
var acObservable = list as IAcObservableCollection;
acObservable?.BeginUpdate();
@ -1189,9 +1189,8 @@ public static partial class AcBinaryDeserializer
private static object ReadDictionaryAsObject(ref BinaryDeserializationContext context, Type keyType, Type valueType, int depth)
{
var dictType = DictionaryGenericType.MakeGenericType(keyType, valueType);
var dict = (IDictionary)Activator.CreateInstance(dictType)!;
var count = (int)context.ReadVarUInt();
var dict = (IDictionary)Activator.CreateInstance(dictType, count)!;
var nextDepth = depth + 1;
for (int i = 0; i < count; i++)

View File

@ -281,7 +281,7 @@ public static partial class AcJsonDeserializer
if (targetType.IsArray)
{
var list = GetOrCreateListFactory(elementType)();
var list = GetOrCreateListFactory(elementType)(0);
foreach (var item in element.EnumerateArray())
list.Add(ReadValue(item, elementType, context, nextDepth));
@ -298,7 +298,7 @@ public static partial class AcJsonDeserializer
}
catch { /* Fallback to List<T> */ }
targetList ??= GetOrCreateListFactory(elementType)();
targetList ??= GetOrCreateListFactory(elementType)(0);
var acObservable = targetList as IAcObservableCollection;
acObservable?.BeginUpdate();

View File

@ -322,7 +322,7 @@ public static partial class AcJsonDeserializer
if (elementType == null) return null;
var nextDepth = depth + 1;
var list = GetOrCreateListFactory(elementType)();
var list = GetOrCreateListFactory(elementType)(0);
while (reader.Read())
{