diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index e1d4a23441..b305a9aaa2 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -3,9 +3,7 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Reactive.Subjects; -using System.Reflection; using Avalonia.Data; using Avalonia.Utilities; @@ -602,7 +600,7 @@ namespace Avalonia return result; } - currentType = currentType.GetTypeInfo().BaseType; + currentType = currentType.BaseType; } _metadataCache[type] = _defaultMetadata; diff --git a/src/Avalonia.Base/Utilities/TypeUtilities.cs b/src/Avalonia.Base/Utilities/TypeUtilities.cs index 3cf05509d4..d1393a9c0d 100644 --- a/src/Avalonia.Base/Utilities/TypeUtilities.cs +++ b/src/Avalonia.Base/Utilities/TypeUtilities.cs @@ -92,8 +92,7 @@ namespace Avalonia.Utilities /// True if the type accepts null values; otherwise false. public static bool AcceptsNull(Type type) { - var t = type.GetTypeInfo(); - return !t.IsValueType || (t.IsGenericType && (t.GetGenericTypeDefinition() == typeof(Nullable<>))); + return !type.IsValueType || IsNullableType(type); } /// @@ -119,10 +118,8 @@ namespace Avalonia.Utilities } var from = value.GetType(); - var fromTypeInfo = from.GetTypeInfo(); - var toTypeInfo = to.GetTypeInfo(); - if (toTypeInfo.IsAssignableFrom(fromTypeInfo)) + if (to.IsAssignableFrom(from)) { result = value; return true; @@ -134,7 +131,7 @@ namespace Avalonia.Utilities return true; } - if (toTypeInfo.IsEnum && from == typeof(string)) + if (to.IsEnum && from == typeof(string)) { if (Enum.IsDefined(to, (string)value)) { @@ -143,7 +140,7 @@ namespace Avalonia.Utilities } } - if (!fromTypeInfo.IsEnum && toTypeInfo.IsEnum) + if (!from.IsEnum && to.IsEnum) { result = null; @@ -154,7 +151,7 @@ namespace Avalonia.Utilities } } - if (fromTypeInfo.IsEnum && IsNumeric(to)) + if (from.IsEnum && IsNumeric(to)) { try { @@ -223,10 +220,8 @@ namespace Avalonia.Utilities } var from = value.GetType(); - var fromTypeInfo = from.GetTypeInfo(); - var toTypeInfo = to.GetTypeInfo(); - if (toTypeInfo.IsAssignableFrom(fromTypeInfo)) + if (to.IsAssignableFrom(from)) { result = value; return true; @@ -307,9 +302,7 @@ namespace Avalonia.Utilities /// The default value. public static object Default(Type type) { - var typeInfo = type.GetTypeInfo(); - - if (typeInfo.IsValueType) + if (type.IsValueType) { return Activator.CreateInstance(type); } @@ -335,9 +328,11 @@ namespace Avalonia.Utilities return false; } - if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) + Type underlyingType = Nullable.GetUnderlyingType(type); + + if (underlyingType != null) { - return IsNumeric(Nullable.GetUnderlyingType(type)); + return IsNumeric(underlyingType); } else { @@ -352,6 +347,11 @@ namespace Avalonia.Utilities Explicit = 2 } + private static bool IsNullableType(Type type) + { + return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); + } + private static MethodInfo FindTypeConversionOperatorMethod(Type fromType, Type toType, OperatorType operatorType) { const string implicitName = "op_Implicit"; 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.Native/PlatformThreadingInterface.cs b/src/Avalonia.Native/PlatformThreadingInterface.cs index 5ddaf83ed4..ba8cc83e83 100644 --- a/src/Avalonia.Native/PlatformThreadingInterface.cs +++ b/src/Avalonia.Native/PlatformThreadingInterface.cs @@ -60,6 +60,7 @@ namespace Avalonia.Native public void RunLoop(CancellationToken cancellationToken) { + _exceptionDispatchInfo?.Throw(); var l = new object(); _exceptionCancellationSource = new CancellationTokenSource(); diff --git a/src/Avalonia.Styling/Styling/Selector.cs b/src/Avalonia.Styling/Styling/Selector.cs index af209ea970..7d4e92baeb 100644 --- a/src/Avalonia.Styling/Styling/Selector.cs +++ b/src/Avalonia.Styling/Styling/Selector.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using Avalonia.Utilities; namespace Avalonia.Styling { @@ -41,7 +43,8 @@ namespace Avalonia.Styling /// A . public SelectorMatch Match(IStyleable control, bool subscribe = true) { - var inputs = new List>(); + ValueSingleOrList> inputs = default; + var selector = this; var alwaysThisType = true; var hitCombinator = false; @@ -66,19 +69,25 @@ namespace Avalonia.Styling } else if (match.Result == SelectorMatchResult.Sometimes) { + Debug.Assert(match.Activator != null); + inputs.Add(match.Activator); } selector = selector.MovePrevious(); } - if (inputs.Count > 0) + if (inputs.HasList) + { + return new SelectorMatch(StyleActivator.And(inputs.List)); + } + else if (inputs.IsSingle) { - return new SelectorMatch(StyleActivator.And(inputs)); + return new SelectorMatch(inputs.Single); } else { - return alwaysThisType && !hitCombinator ? + return alwaysThisType && !hitCombinator ? SelectorMatch.AlwaysThisType : SelectorMatch.AlwaysThisInstance; } diff --git a/tests/Avalonia.Benchmarks/Styling/SelectorBenchmark.cs b/tests/Avalonia.Benchmarks/Styling/SelectorBenchmark.cs new file mode 100644 index 0000000000..0ac96c1103 --- /dev/null +++ b/tests/Avalonia.Benchmarks/Styling/SelectorBenchmark.cs @@ -0,0 +1,52 @@ +using Avalonia.Controls; +using Avalonia.Styling; +using BenchmarkDotNet.Attributes; + +namespace Avalonia.Benchmarks.Styling +{ + [MemoryDiagnoser] + public class SelectorBenchmark + { + private readonly Control _notMatchingControl; + private readonly Calendar _matchingControl; + private readonly Selector _isCalendarSelector; + private readonly Selector _classSelector; + + public SelectorBenchmark() + { + _notMatchingControl = new Control(); + _matchingControl = new Calendar(); + + const string className = "selector-class"; + + _matchingControl.Classes.Add(className); + + _isCalendarSelector = Selectors.Is(null); + _classSelector = Selectors.Class(null, className); + } + + [Benchmark] + public SelectorMatch IsSelector_NoMatch() + { + return _isCalendarSelector.Match(_notMatchingControl); + } + + [Benchmark] + public SelectorMatch IsSelector_Match() + { + return _isCalendarSelector.Match(_matchingControl); + } + + [Benchmark] + public SelectorMatch ClassSelector_NoMatch() + { + return _classSelector.Match(_notMatchingControl); + } + + [Benchmark] + public SelectorMatch ClassSelector_Match() + { + return _classSelector.Match(_matchingControl); + } + } +}