diff --git a/src/Avalonia.Base/Controls/ResourceDictionary.cs b/src/Avalonia.Base/Controls/ResourceDictionary.cs
index 285031e256..83264a477d 100644
--- a/src/Avalonia.Base/Controls/ResourceDictionary.cs
+++ b/src/Avalonia.Base/Controls/ResourceDictionary.cs
@@ -258,6 +258,24 @@ namespace Avalonia.Controls
return false;
}
+ ///
+ /// Ensures that the resource dictionary can hold up to entries without
+ /// any further expansion of its backing storage.
+ ///
+ /// This method may have no effect when targeting .NET Standard 2.0.
+ public void EnsureCapacity(int capacity)
+ {
+ if (_inner is null)
+ {
+ _inner = new(capacity);
+ return;
+ }
+
+#if !NETSTANDARD2_0
+ Inner.EnsureCapacity(capacity);
+#endif
+ }
+
public IEnumerator> GetEnumerator()
{
return _inner?.GetEnumerator() ?? Enumerable.Empty>().GetEnumerator();
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
index 71d77b12ed..9d1f439bab 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
@@ -88,6 +88,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
Transformers.Add(new AvaloniaXamlIlControlTemplatePriorityTransformer());
Transformers.Add(new AvaloniaXamlIlMetadataRemover());
+ Transformers.Add(new AvaloniaXamlIlEnsureResourceDictionaryCapacityTransformer());
Transformers.Add(new AvaloniaXamlIlRootObjectScope());
Emitters.Add(new AvaloniaNameScopeRegistrationXamlIlNodeEmitter());
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs
index 61a12aa72d..20123f2e32 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlMergeResourceGroupTransformer.cs
@@ -162,16 +162,27 @@ internal class XamlMergeResourceGroupTransformer : IXamlAstGroupTransformer
&& ThemeVariantNodeEquals(context, key, sameKeyPrevAssignmentNode.Values[0]))
{
sameKeyPrevValueGroup.Children.AddRange(valueGroup.Children);
+ FixEnsureCapacityNodes(context, sameKeyPrevValueGroup);
children.RemoveAt(i);
break;
}
}
}
}
-
+
+ FixEnsureCapacityNodes(context, resourceDictionaryManipulation);
+
return node;
}
+ // // Removes all existing EnsureCapacityNode (from the merged dictionaries) and adds a new one.
+ private static void FixEnsureCapacityNodes(AstGroupTransformationContext context, XamlManipulationGroupNode manipulation)
+ {
+ var children = manipulation.Children;
+ children.RemoveAll(c => c is AvaloniaXamlIlEnsureResourceDictionaryCapacityTransformer.EnsureCapacityNode);
+ new AvaloniaXamlIlEnsureResourceDictionaryCapacityTransformer().Apply(context, manipulation);
+ }
+
public static bool ThemeVariantNodeEquals(AstGroupTransformationContext context, IXamlAstValueNode left, IXamlAstValueNode right)
{
if (left is XamlConstantNode leftConst
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlEnsureResourceDictionaryCapacityTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlEnsureResourceDictionaryCapacityTransformer.cs
new file mode 100644
index 0000000000..76781046b2
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlEnsureResourceDictionaryCapacityTransformer.cs
@@ -0,0 +1,142 @@
+#nullable enable
+
+using System.Collections.Generic;
+using XamlX.Ast;
+using XamlX.Emit;
+using XamlX.IL;
+using XamlX.Transform;
+using XamlX.TypeSystem;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
+
+///
+/// Adds a call to EnsureCapacity before adding items to a ResourceDictionary.
+///
+internal sealed class AvaloniaXamlIlEnsureResourceDictionaryCapacityTransformer : IXamlAstTransformer
+{
+ private readonly HashSet _processedGroups = new();
+
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+ {
+ if (node is XamlManipulationGroupNode group)
+ Apply(context, group);
+
+ return node;
+ }
+
+ public void Apply(AstTransformationContext context, XamlManipulationGroupNode group)
+ {
+ var info = GetResourcesInfo(group, context.GetAvaloniaTypes());
+
+ if (info.Mode != ResourcesMode.None && info.Count >= 2)
+ group.Children.Insert(0, new EnsureCapacityNode(group, info.Count, info.ResourcesGetter));
+ }
+
+ private ResourcesInfo GetResourcesInfo(IXamlAstManipulationNode node, AvaloniaXamlIlWellKnownTypes types)
+ => node switch
+ {
+ XamlPropertyAssignmentNode propertyAssignment => GetResourcesInfo(propertyAssignment, types),
+ XamlManipulationGroupNode group => GetResourcesInfo(group, types),
+ _ => default
+ };
+
+ private ResourcesInfo GetResourcesInfo(XamlManipulationGroupNode node, AvaloniaXamlIlWellKnownTypes types)
+ {
+ if (!_processedGroups.Add(node))
+ return default;
+
+ ResourcesInfo groupInfo = default;
+
+ foreach (var child in node.Children)
+ {
+ var childInfo = GetResourcesInfo(child, types);
+
+ if (childInfo.Mode == ResourcesMode.None)
+ continue;
+
+ if (groupInfo.Mode == ResourcesMode.None)
+ groupInfo = new ResourcesInfo(childInfo.Mode, childInfo.ResourcesGetter);
+ else if (groupInfo.Mode != childInfo.Mode || groupInfo.ResourcesGetter != childInfo.ResourcesGetter)
+ return default;
+
+ groupInfo.Count += childInfo.Count;
+ }
+
+ return groupInfo;
+ }
+
+ private static ResourcesInfo GetResourcesInfo(XamlPropertyAssignmentNode node, AvaloniaXamlIlWellKnownTypes types)
+ => node.Property.Name switch
+ {
+ "Content" when node.Property.DeclaringType == types.ResourceDictionary
+ => new ResourcesInfo(ResourcesMode.ResourceDictionaryContent, null) { Count = 1 },
+ "Resources" when types.IResourceDictionary.IsAssignableFrom(node.Property.Getter.ReturnType)
+ => new ResourcesInfo(ResourcesMode.ElementResources, node.Property.Getter) { Count = 1 },
+ _
+ => default
+ };
+
+ private struct ResourcesInfo
+ {
+ public ResourcesMode Mode { get; }
+ public IXamlMethod? ResourcesGetter { get; }
+ public int Count { get; set; }
+
+ public ResourcesInfo(ResourcesMode mode, IXamlMethod? resourcesGetter)
+ {
+ Mode = mode;
+ ResourcesGetter = resourcesGetter;
+ }
+ }
+
+ private enum ResourcesMode
+ {
+ None,
+ ResourceDictionaryContent,
+ ElementResources
+ }
+
+ public sealed class EnsureCapacityNode : XamlAstNode, IXamlAstManipulationNode, IXamlAstILEmitableNode
+ {
+ private readonly int _capacity;
+ private readonly IXamlMethod? _resourcesGetter;
+
+ public EnsureCapacityNode(IXamlLineInfo lineInfo, int capacity, IXamlMethod? resourcesGetter)
+ : base(lineInfo)
+ {
+ _capacity = capacity;
+ _resourcesGetter = resourcesGetter;
+ }
+
+ public XamlILNodeEmitResult Emit(
+ XamlEmitContext context,
+ IXamlILEmitter codeGen)
+ {
+ var types = context.GetAvaloniaTypes();
+ var exitLabel = codeGen.DefineLabel();
+
+ using var local = codeGen.LocalsPool.GetLocal(types.ResourceDictionary);
+
+ if (_resourcesGetter is not null)
+ codeGen.EmitCall(_resourcesGetter);
+
+ codeGen
+ // if (value is not ResourceDictionary local) goto exit;
+ .Isinst(types.ResourceDictionary)
+ .Stloc(local.Local)
+ .Ldloc(local.Local)
+ .Brfalse(exitLabel)
+ // local.EnsureCapacity(local.Count + `_capacity`);
+ .Ldloc(local.Local)
+ .Dup()
+ .EmitCall(types.ResourceDictionaryGetCount)
+ .Ldc_I4(_capacity)
+ .Add()
+ .EmitCall(types.ResourceDictionaryEnsureCapacity)
+ // exit:
+ .MarkLabel(exitLabel);
+
+ return XamlILNodeEmitResult.Void(1);
+ }
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
index 1b26f6c899..89d2518e2d 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
@@ -114,6 +114,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType IResourceDictionary { get; }
public IXamlType ResourceDictionary { get; }
public IXamlMethod ResourceDictionaryDeferredAdd { get; }
+ public IXamlMethod ResourceDictionaryEnsureCapacity { get; }
+ public IXamlMethod ResourceDictionaryGetCount { get; }
public IXamlType IThemeVariantProvider { get; }
public IXamlType UriKind { get; }
public IXamlConstructor UriConstructor { get; }
@@ -262,6 +264,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
cfg.TypeSystem.GetType("System.Func`2").MakeGenericType(
cfg.TypeSystem.GetType("System.IServiceProvider"),
XamlIlTypes.Object));
+ ResourceDictionaryEnsureCapacity = ResourceDictionary.FindMethod("EnsureCapacity", XamlIlTypes.Void, true, XamlIlTypes.Int32);
+ ResourceDictionaryGetCount = ResourceDictionary.FindMethod("get_Count", XamlIlTypes.Int32, true);
IThemeVariantProvider = cfg.TypeSystem.GetType("Avalonia.Controls.IThemeVariantProvider");
UriKind = cfg.TypeSystem.GetType("System.UriKind");
UriConstructor = Uri.GetConstructor(new List() { cfg.WellKnownTypes.String, UriKind });