diff --git a/src/Avalonia.Base/Utilities/ValueSingleOrList.cs b/src/Avalonia.Base/Utilities/ValueSingleOrList.cs
new file mode 100644
index 0000000000..dc32cedb76
--- /dev/null
+++ b/src/Avalonia.Base/Utilities/ValueSingleOrList.cs
@@ -0,0 +1,94 @@
+using System.Collections.Generic;
+
+namespace Avalonia.Utilities
+{
+ ///
+ /// A list like struct optimized for holding zero or one items.
+ ///
+ /// The type of items held in the list.
+ ///
+ /// Once more than value has been added to this storage it will switch to using internally.
+ ///
+ public ref struct ValueSingleOrList
+ {
+ private bool _isSingleSet;
+
+ ///
+ /// Single contained value. Only valid if is set.
+ ///
+ public T Single { get; private set; }
+
+ ///
+ /// List of values.
+ ///
+ public List List { get; private set; }
+
+ ///
+ /// If this struct is backed by a list.
+ ///
+ public bool HasList => List != null;
+
+ ///
+ /// If this struct contains only single value and storage was not promoted to a list.
+ ///
+ public bool IsSingle => List == null && _isSingleSet;
+
+ ///
+ /// Adds a value.
+ ///
+ /// Value to add.
+ public void Add(T value)
+ {
+ if (List != null)
+ {
+ List.Add(value);
+ }
+ else
+ {
+ if (!_isSingleSet)
+ {
+ Single = value;
+
+ _isSingleSet = true;
+ }
+ else
+ {
+ List = new List();
+
+ List.Add(Single);
+ List.Add(value);
+
+ Single = default;
+ }
+ }
+ }
+
+ ///
+ /// Removes a value.
+ ///
+ /// Value to remove.
+ public bool Remove(T value)
+ {
+ if (List != null)
+ {
+ return List.Remove(value);
+ }
+
+ if (!_isSingleSet)
+ {
+ return false;
+ }
+
+ if (EqualityComparer.Default.Equals(Single, value))
+ {
+ Single = default;
+
+ _isSingleSet = false;
+
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/Avalonia.Styling/Styling/Selector.cs b/src/Avalonia.Styling/Styling/Selector.cs
index 52c6ae4027..7d4e92baeb 100644
--- a/src/Avalonia.Styling/Styling/Selector.cs
+++ b/src/Avalonia.Styling/Styling/Selector.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using Avalonia.Utilities;
namespace Avalonia.Styling
{
@@ -42,8 +43,7 @@ namespace Avalonia.Styling
/// A .
public SelectorMatch Match(IStyleable control, bool subscribe = true)
{
- List> inputs = null;
- IObservable singleInput = null;
+ ValueSingleOrList> inputs = default;
var selector = this;
var alwaysThisType = true;
@@ -71,40 +71,23 @@ namespace Avalonia.Styling
{
Debug.Assert(match.Activator != null);
- if (inputs != null)
- {
- inputs.Add(match.Activator);
- }
- else
- {
- if (singleInput == null)
- {
- singleInput = match.Activator;
- }
- else
- {
- inputs = new List>();
-
- inputs.Add(singleInput);
- inputs.Add(match.Activator);
- }
- }
+ inputs.Add(match.Activator);
}
selector = selector.MovePrevious();
}
- if (inputs != null)
+ if (inputs.HasList)
{
- return new SelectorMatch(StyleActivator.And(inputs));
+ return new SelectorMatch(StyleActivator.And(inputs.List));
}
- else if (singleInput != null)
+ else if (inputs.IsSingle)
{
- return new SelectorMatch(singleInput);
+ return new SelectorMatch(inputs.Single);
}
else
{
- return alwaysThisType && !hitCombinator ?
+ return alwaysThisType && !hitCombinator ?
SelectorMatch.AlwaysThisType :
SelectorMatch.AlwaysThisInstance;
}