diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs
index b7ddc87f73..cc93208e51 100644
--- a/src/Avalonia.Base/AvaloniaObject.cs
+++ b/src/Avalonia.Base/AvaloniaObject.cs
@@ -749,6 +749,24 @@ namespace Avalonia
RaisePropertyChanged(property, oldValue, newValue, BindingPriority.LocalValue, true);
}
+ ///
+ /// This is an optimized path for .
+ /// This will reuse the event args in situations where many allocations would otherwise happen.
+ ///
+ /// Avalonia property change args
+ /// INPC event args/
+ internal void RaisePropertyChanged(AvaloniaPropertyChangedEventArgs args, PropertyChangedEventArgs? inpcArgs)
+ {
+ OnPropertyChangedCore(args);
+
+ if (args.IsEffectiveValueChange && inpcArgs is not null)
+ {
+ args.Property.NotifyChanged(args);
+ _propertyChanged?.Invoke(this, args);
+ _inpcChanged?.Invoke(this, inpcArgs);
+ }
+ }
+
///
/// Raises the event.
///
diff --git a/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs b/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs
index 997a363c26..3d04fe74f1 100644
--- a/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs
+++ b/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs
@@ -31,7 +31,7 @@ namespace Avalonia
/// Gets the that the property changed on.
///
/// The sender object.
- public AvaloniaObject Sender { get; }
+ public AvaloniaObject Sender { get; private set; }
///
/// Gets the property that changed.
@@ -60,6 +60,16 @@ namespace Avalonia
public BindingPriority Priority { get; private set; }
internal bool IsEffectiveValueChange { get; private set; }
+
+ ///
+ /// Sets the Sender property.
+ /// This is purely for reuse in some code paths where multiple allocations may occur.
+ ///
+ /// The sender object.
+ internal void SetSender(AvaloniaObject sender)
+ {
+ Sender = sender;
+ }
protected abstract AvaloniaProperty GetProperty();
protected abstract object? GetOldValue();
diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs
index 9600e2d3bc..1af99366e7 100644
--- a/src/Avalonia.Base/PropertyStore/ValueStore.cs
+++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
@@ -590,10 +591,13 @@ namespace Avalonia.PropertyStore
return;
var count = children.Count;
+
+ var apArgs = new AvaloniaPropertyChangedEventArgs(Owner, property, oldValue, value.Value, BindingPriority.Inherited, true);
+ var incpArgs = new PropertyChangedEventArgs(property.Name);
for (var i = 0; i < count; ++i)
{
- children[i].GetValueStore().OnAncestorInheritedValueChanged(property, oldValue, value.Value);
+ children[i].GetValueStore().OnAncestorInheritedValueChanged(apArgs, incpArgs);
}
}
@@ -614,9 +618,12 @@ namespace Avalonia.PropertyStore
{
var count = children.Count;
+ var apArgs = new AvaloniaPropertyChangedEventArgs(Owner, property, oldValue, newValue, BindingPriority.Inherited, true);
+ var incpArgs = new PropertyChangedEventArgs(property.Name);
+
for (var i = 0; i < count; ++i)
{
- children[i].GetValueStore().OnAncestorInheritedValueChanged(property, oldValue, newValue);
+ children[i].GetValueStore().OnAncestorInheritedValueChanged(apArgs, incpArgs);
}
}
}
@@ -639,33 +646,24 @@ namespace Avalonia.PropertyStore
}
}
}
-
+
///
/// Called when an inherited property changes on the value store of the inheritance ancestor.
///
/// The property type.
- /// The property.
- /// The old value of the property.
- /// The new value of the property.
- public void OnAncestorInheritedValueChanged(
- StyledProperty property,
- T oldValue,
- T newValue)
+ /// Avalonia Property EventArgs to reuse.
+ /// PropertyChangedEventArgs to reuse
+ public void OnAncestorInheritedValueChanged(AvaloniaPropertyChangedEventArgs apArgs, PropertyChangedEventArgs? args)
{
- Debug.Assert(property.Inherits);
-
+
// If the inherited value is set locally, propagation stops here.
- if (_effectiveValues.ContainsKey(property))
+ if (_effectiveValues.ContainsKey(apArgs.Property))
return;
- using var notifying = PropertyNotifying.Start(Owner, property);
+ using var notifying = PropertyNotifying.Start(Owner, apArgs.Property);
- Owner.RaisePropertyChanged(
- property,
- oldValue,
- newValue,
- BindingPriority.Inherited,
- true);
+ apArgs.SetSender(Owner);
+ Owner.RaisePropertyChanged(apArgs, args);
var children = Owner.GetInheritanceChildren();
@@ -676,7 +674,7 @@ namespace Avalonia.PropertyStore
for (var i = 0; i < count; ++i)
{
- children[i].GetValueStore().OnAncestorInheritedValueChanged(property, oldValue, newValue);
+ children[i].GetValueStore().OnAncestorInheritedValueChanged(apArgs, args);
}
}
diff --git a/tests/Avalonia.Benchmarks/Data/InheritedProperties.cs b/tests/Avalonia.Benchmarks/Data/InheritedProperties.cs
new file mode 100644
index 0000000000..2579a228c9
--- /dev/null
+++ b/tests/Avalonia.Benchmarks/Data/InheritedProperties.cs
@@ -0,0 +1,46 @@
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using Avalonia.Controls;
+using Avalonia.UnitTests;
+using BenchmarkDotNet.Attributes;
+
+namespace Avalonia.Benchmarks.Data;
+
+[MemoryDiagnoser]
+public class InheritedProperties
+{
+ private readonly TestRoot _root;
+ private readonly List _controls = new();
+
+ public InheritedProperties()
+ {
+ var panel = new StackPanel();
+
+ _root = new TestRoot
+ {
+ Child = panel,
+ Renderer = new NullRenderer()
+ };
+
+ _controls.Add(panel);
+ _controls = ControlHierarchyCreator.CreateChildren(_controls, panel, 3, 5, 5);
+
+ _root.LayoutManager.ExecuteInitialLayoutPass();
+ }
+
+ [Benchmark, MethodImpl(MethodImplOptions.NoInlining)]
+ public void ChangeDataContext()
+ {
+ TestDataContext[] dataContexts = [new(), new(), new()];
+
+ for (int i = 0; i < 100; i++)
+ {
+ for (int j = 0; j < dataContexts.Length; j++)
+ {
+ _root.DataContext = dataContexts[j];
+ }
+ }
+ }
+
+ public class TestDataContext;
+}