diff --git a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs index 84ef0fb695..8fc2a7b77c 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs @@ -12,7 +12,7 @@ namespace Avalonia.Data.Core.Plugins public class InpcPropertyAccessorPlugin : IPropertyAccessorPlugin { /// - public bool Match(object obj, string propertyName) => true; + public bool Match(object obj, string propertyName) => GetPropertyWithName(obj.GetType(), propertyName) != null; /// /// Starts monitoring the value of a property on an object. @@ -30,10 +30,7 @@ namespace Avalonia.Data.Core.Plugins reference.TryGetTarget(out object instance); - const BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Public | - BindingFlags.Static | BindingFlags.Instance; - - var p = instance.GetType().GetProperty(propertyName, bindingFlags); + var p = GetPropertyWithName(instance.GetType(), propertyName); if (p != null) { @@ -47,6 +44,14 @@ namespace Avalonia.Data.Core.Plugins } } + private static PropertyInfo GetPropertyWithName(Type type, string propertyName) + { + const BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Public | + BindingFlags.Static | BindingFlags.Instance; + + return type.GetProperty(propertyName, bindingFlags); + } + private class Accessor : PropertyAccessorBase, IWeakSubscriber { private readonly WeakReference _reference; diff --git a/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs b/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs index e20685b1dd..a0868152f6 100644 --- a/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs +++ b/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs @@ -62,8 +62,13 @@ namespace Avalonia.Data.Core if (accessor == null) { - throw new NotSupportedException( - $"Could not find a matching property accessor for {PropertyName}."); + reference.TryGetTarget(out object instance); + + var message = $"Could not find a matching property accessor for '{PropertyName}' on '{instance}'"; + + var exception = new MissingMemberException(message); + + accessor = new PropertyError(new BindingNotification(exception, BindingErrorType.Error)); } _accessor = accessor; diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs index 6e175707e4..df871a67b4 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs @@ -322,7 +322,7 @@ namespace Avalonia.Base.UnitTests.Data.Core { "bar", new BindingNotification( - new MissingMemberException("Could not find CLR property 'Bar' on 'Avalonia.Base.UnitTests.Data.Core.ExpressionObserverTests_Property+WithoutBar'"), + new MissingMemberException("Could not find a matching property accessor for 'Bar' on 'Avalonia.Base.UnitTests.Data.Core.ExpressionObserverTests_Property+WithoutBar'"), BindingErrorType.Error), "baz", }, diff --git a/tests/Avalonia.Benchmarks/Data/AccessorTestObject.cs b/tests/Avalonia.Benchmarks/Data/AccessorTestObject.cs new file mode 100644 index 0000000000..0039f5670c --- /dev/null +++ b/tests/Avalonia.Benchmarks/Data/AccessorTestObject.cs @@ -0,0 +1,47 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; + +namespace Avalonia.Benchmarks.Data +{ + internal class AccessorTestObject : INotifyPropertyChanged + { + private string _test; + + public string Test + { + get => _test; + set + { + if (_test == value) + { + return; + } + + _test = value; + + OnPropertyChanged(); + } + } + + public event PropertyChangedEventHandler PropertyChanged; + + public void Execute() + { + } + + public void Execute(object p0) + { + } + + public void Execute(object p0, object p1) + { + } + + [NotifyPropertyChangedInvocator] + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/tests/Avalonia.Benchmarks/Data/PropertyAccessorBenchmarks.cs b/tests/Avalonia.Benchmarks/Data/PropertyAccessorBenchmarks.cs index 3d5a4029bb..6c5e0d3b53 100644 --- a/tests/Avalonia.Benchmarks/Data/PropertyAccessorBenchmarks.cs +++ b/tests/Avalonia.Benchmarks/Data/PropertyAccessorBenchmarks.cs @@ -1,9 +1,6 @@ using System; -using System.ComponentModel; -using System.Runtime.CompilerServices; using Avalonia.Data.Core.Plugins; using BenchmarkDotNet.Attributes; -using JetBrains.Annotations; namespace Avalonia.Benchmarks.Data { @@ -12,7 +9,7 @@ namespace Avalonia.Benchmarks.Data { private readonly InpcPropertyAccessorPlugin _inpcPlugin = new InpcPropertyAccessorPlugin(); private readonly MethodAccessorPlugin _methodPlugin = new MethodAccessorPlugin(); - private readonly TestObject _targetStrongRef = new TestObject(); + private readonly AccessorTestObject _targetStrongRef = new AccessorTestObject(); private readonly WeakReference _targetWeakRef; public PropertyAccessorBenchmarks() @@ -23,66 +20,25 @@ namespace Avalonia.Benchmarks.Data [Benchmark] public void InpcAccessorMatch() { - _inpcPlugin.Match(_targetWeakRef, nameof(TestObject.Test)); + _inpcPlugin.Match(_targetWeakRef, nameof(AccessorTestObject.Test)); } [Benchmark] public void InpcAccessorStart() { - _inpcPlugin.Start(_targetWeakRef, nameof(TestObject.Test)); + _inpcPlugin.Start(_targetWeakRef, nameof(AccessorTestObject.Test)); } [Benchmark] public void MethodAccessorMatch() { - _methodPlugin.Match(_targetWeakRef, nameof(TestObject.Execute)); + _methodPlugin.Match(_targetWeakRef, nameof(AccessorTestObject.Execute)); } [Benchmark] public void MethodAccessorStart() { - _methodPlugin.Start(_targetWeakRef, nameof(TestObject.Execute)); - } - - private class TestObject : INotifyPropertyChanged - { - private string _test; - - public string Test - { - get => _test; - set - { - if (_test == value) - { - return; - } - - _test = value; - - OnPropertyChanged(); - } - } - - public void Execute() - { - } - - public void Execute(object p0) - { - } - - public void Execute(object p0, object p1) - { - } - - public event PropertyChangedEventHandler PropertyChanged; - - [NotifyPropertyChangedInvocator] - protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } + _methodPlugin.Start(_targetWeakRef, nameof(AccessorTestObject.Execute)); } } } diff --git a/tests/Avalonia.Benchmarks/Data/PropertyAccessorPluginBenchmarks.cs b/tests/Avalonia.Benchmarks/Data/PropertyAccessorPluginBenchmarks.cs new file mode 100644 index 0000000000..75e3269e3d --- /dev/null +++ b/tests/Avalonia.Benchmarks/Data/PropertyAccessorPluginBenchmarks.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; +using Avalonia.Data.Core.Plugins; +using BenchmarkDotNet.Attributes; + +namespace Avalonia.Benchmarks.Data +{ + [MemoryDiagnoser, InProcess] + public class PropertyAccessorPluginBenchmarks + { + private readonly AccessorTestObject _targetStrongRef = new AccessorTestObject(); + + private readonly List _oldPlugins; + private readonly List _newPlugins; + + public PropertyAccessorPluginBenchmarks() + { + _oldPlugins = new List + { + new AvaloniaPropertyAccessorPlugin(), + new MethodAccessorPlugin(), + new InpcPropertyAccessorPlugin() + }; + + _newPlugins = new List + { + new AvaloniaPropertyAccessorPlugin(), + new InpcPropertyAccessorPlugin(), + new MethodAccessorPlugin() + }; + } + + [Benchmark] + public void MatchAccessorOld() + { + var propertyName = nameof(AccessorTestObject.Test); + + foreach (IPropertyAccessorPlugin x in _oldPlugins) + { + if (x.Match(_targetStrongRef, propertyName)) + { + break; + } + } + } + + [Benchmark] + public void MatchAccessorNew() + { + var propertyName = nameof(AccessorTestObject.Test); + + foreach (IPropertyAccessorPlugin x in _newPlugins) + { + if (x.Match(_targetStrongRef, propertyName)) + { + break; + } + } + } + } +} diff --git a/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Property.cs b/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Property.cs index 4f7264f2f2..8cf74c36f4 100644 --- a/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Property.cs +++ b/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Property.cs @@ -22,7 +22,7 @@ namespace Avalonia.Markup.UnitTests.Parsers Assert.Equal( new BindingNotification( - new MissingMemberException("Could not find CLR property 'Baz' on '1'"), BindingErrorType.Error), + new MissingMemberException("Could not find a matching property accessor for 'Baz' on '1'"), BindingErrorType.Error), result); GC.KeepAlive(data);