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