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