diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs
index bba9685ed8..2f3f672d54 100644
--- a/src/Avalonia.Base/StyledElement.cs
+++ b/src/Avalonia.Base/StyledElement.cs
@@ -344,10 +344,14 @@ namespace Avalonia
/// Applies styling to the control if the control is initialized and styling is not
/// already applied.
///
+ ///
+ /// The styling system will automatically apply styling when required, so it should not
+ /// usually be necessary to call this method manually.
+ ///
///
/// A value indicating whether styling is now applied to the control.
///
- protected bool ApplyStyling()
+ public bool ApplyStyling()
{
if (_initCount == 0 && !_styled)
{
diff --git a/tests/Avalonia.Benchmarks/Styling/StyleAttachBenchmark.cs b/tests/Avalonia.Benchmarks/Styling/StyleAttachBenchmark.cs
deleted file mode 100644
index 7dad517e51..0000000000
--- a/tests/Avalonia.Benchmarks/Styling/StyleAttachBenchmark.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-using System;
-using System.Runtime.CompilerServices;
-using Avalonia.Controls;
-using Avalonia.Styling;
-using Avalonia.UnitTests;
-using BenchmarkDotNet.Attributes;
-
-namespace Avalonia.Benchmarks.Styling
-{
- [MemoryDiagnoser]
- public class StyleAttachBenchmark : IDisposable
- {
- private readonly IDisposable _app;
- private readonly TestRoot _root;
- private readonly TextBox _control;
-
- public StyleAttachBenchmark()
- {
- _app = UnitTestApplication.Start(
- TestServices.StyledWindow.With(
- renderInterface: new NullRenderingPlatform(),
- threadingInterface: new NullThreadingPlatform()));
-
- _root = new TestRoot(true, null)
- {
- Renderer = new NullRenderer(),
- };
-
- _control = new TextBox();
- }
-
- [Benchmark]
- [MethodImpl(MethodImplOptions.NoInlining)]
- public void AttachTextBoxStyles()
- {
- var styles = UnitTestApplication.Current.Styles;
-
- styles.TryAttach(_control, UnitTestApplication.Current);
- ((IStyleable)_control).DetachStyles();
- }
-
- public void Dispose()
- {
- _app.Dispose();
- }
- }
-}
diff --git a/tests/Avalonia.Benchmarks/Styling/Style_Apply_Detach_Complex.cs b/tests/Avalonia.Benchmarks/Styling/Style_Apply_Detach_Complex.cs
new file mode 100644
index 0000000000..fa8ad00bf8
--- /dev/null
+++ b/tests/Avalonia.Benchmarks/Styling/Style_Apply_Detach_Complex.cs
@@ -0,0 +1,126 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using Avalonia.Controls;
+using Avalonia.Styling;
+using Avalonia.UnitTests;
+using BenchmarkDotNet.Attributes;
+
+namespace Avalonia.Benchmarks.Styling
+{
+ [MemoryDiagnoser]
+ public class Style_Apply_Detach_Complex : IDisposable
+ {
+ private readonly IDisposable _app;
+ private readonly TestRoot _root;
+ private readonly TextBox _control;
+
+ public Style_Apply_Detach_Complex()
+ {
+ _app = UnitTestApplication.Start(
+ TestServices.StyledWindow.With(
+ renderInterface: new NullRenderingPlatform(),
+ threadingInterface: new NullThreadingPlatform()));
+
+ // Simulate an application with a lot of styles by creating a tree of nested panels,
+ // each with a bunch of styles applied.
+ var (rootPanel, leafPanel) = CreateNestedPanels(10);
+
+ // We're benchmarking how long it takes to apply styles to a TextBox in this situation.
+ _control = new TextBox();
+ leafPanel.Children.Add(_control);
+
+ _root = new TestRoot(true, rootPanel)
+ {
+ Renderer = new NullRenderer(),
+ };
+ }
+
+ [Benchmark]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public void Apply_Detach_Styles()
+ {
+ // Styles will have already been attached when attached to the logical tree, so remove
+ // the styles first.
+ if ((string)_control.Tag != "TextBox")
+ throw new Exception("Invalid benchmark state");
+
+ ((IStyleable)_control).DetachStyles();
+
+ if (_control.Tag is not null)
+ throw new Exception("Invalid benchmark state");
+
+ // Then re-apply the styles.
+ _control.ApplyStyling();
+ }
+
+ public void Dispose()
+ {
+ _app.Dispose();
+ }
+
+ private static (Panel, Panel) CreateNestedPanels(int count)
+ {
+ var root = new Panel();
+ var last = root;
+
+ for (var i = 0; i < count; ++i)
+ {
+ var panel = new Panel();
+ panel.Styles.AddRange(CreateStyles());
+ last.Children.Add(panel);
+ last = panel;
+ }
+
+ return (root, last);
+ }
+
+ private static IEnumerable CreateStyles()
+ {
+ var types = new[]
+ {
+ typeof(Border),
+ typeof(Button),
+ typeof(ButtonSpinner),
+ typeof(Carousel),
+ typeof(CheckBox),
+ typeof(ComboBox),
+ typeof(ContentControl),
+ typeof(Expander),
+ typeof(ItemsControl),
+ typeof(Label),
+ typeof(ListBox),
+ typeof(ProgressBar),
+ typeof(RadioButton),
+ typeof(RepeatButton),
+ typeof(ScrollViewer),
+ typeof(Slider),
+ typeof(Spinner),
+ typeof(SplitView),
+ typeof(TextBox),
+ typeof(ToggleSwitch),
+ typeof(TreeView),
+ typeof(Viewbox),
+ typeof(Window),
+ };
+
+ foreach (var type in types)
+ {
+ yield return new Style(x => x.OfType(type))
+ {
+ Setters = { new Setter(Control.TagProperty, type.Name) }
+ };
+
+ yield return new Style(x => x.OfType(type).Class("foo"))
+ {
+ Setters = { new Setter(Control.TagProperty, type.Name + " foo") }
+ };
+
+ yield return new Style(x => x.OfType(type).Class("bar"))
+ {
+ Setters = { new Setter(Control.TagProperty, type.Name + " bar") }
+ };
+ }
+ }
+ }
+}