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; +}