diff --git a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
index 8fc2a7b77c..3bf6842cd6 100644
--- a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
+++ b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using Avalonia.Utilities;
@@ -11,8 +12,11 @@ namespace Avalonia.Data.Core.Plugins
///
public class InpcPropertyAccessorPlugin : IPropertyAccessorPlugin
{
+ private readonly Dictionary<(Type, string), PropertyInfo> _propertyLookup =
+ new Dictionary<(Type, string), PropertyInfo>();
+
///
- public bool Match(object obj, string propertyName) => GetPropertyWithName(obj.GetType(), propertyName) != null;
+ public bool Match(object obj, string propertyName) => GetFirstPropertyWithName(obj.GetType(), propertyName) != null;
///
/// Starts monitoring the value of a property on an object.
@@ -30,7 +34,7 @@ namespace Avalonia.Data.Core.Plugins
reference.TryGetTarget(out object instance);
- var p = GetPropertyWithName(instance.GetType(), propertyName);
+ var p = GetFirstPropertyWithName(instance.GetType(), propertyName);
if (p != null)
{
@@ -44,12 +48,40 @@ namespace Avalonia.Data.Core.Plugins
}
}
- private static PropertyInfo GetPropertyWithName(Type type, string propertyName)
+ private PropertyInfo GetFirstPropertyWithName(Type type, string propertyName)
{
- const BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Public |
- BindingFlags.Static | BindingFlags.Instance;
+ var key = (type, propertyName);
+
+ if (!_propertyLookup.TryGetValue(key, out PropertyInfo propertyInfo))
+ {
+ propertyInfo = TryFindAndCacheProperty(type, propertyName);
+ }
+
+ return propertyInfo;
+ }
+
+ private PropertyInfo TryFindAndCacheProperty(Type type, string propertyName)
+ {
+ PropertyInfo found = null;
+
+ const BindingFlags bindingFlags =
+ BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance;
+
+ var properties = type.GetProperties(bindingFlags);
+
+ foreach (PropertyInfo propertyInfo in properties)
+ {
+ if (propertyInfo.Name == propertyName)
+ {
+ found = propertyInfo;
+
+ break;
+ }
+ }
+
+ _propertyLookup.Add((type, propertyName), found);
- return type.GetProperty(propertyName, bindingFlags);
+ return found;
}
private class Accessor : PropertyAccessorBase, IWeakSubscriber
diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs
index b4c30e0149..b2bd5ab2e5 100644
--- a/src/Avalonia.Controls/TreeView.cs
+++ b/src/Avalonia.Controls/TreeView.cs
@@ -117,10 +117,8 @@ namespace Avalonia.Controls
if (value != null)
{
if (selectedItems.Count != 1 || selectedItems[0] != value)
- {
- _syncingSelectedItems = true;
- SelectSingleItem(value);
- _syncingSelectedItems = false;
+ {
+ SelectSingleItem(value);
}
}
else if (SelectedItems.Count > 0)
@@ -219,8 +217,12 @@ namespace Avalonia.Controls
private void SelectSingleItem(object item)
{
- SelectedItems.Clear();
+ _syncingSelectedItems = true;
+ SelectedItems.Clear();
SelectedItems.Add(item);
+ _syncingSelectedItems = false;
+
+ SetAndRaise(SelectedItemProperty, ref _selectedItem, item);
}
///
diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs
index 784046ac0b..32cdd21e04 100644
--- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs
+++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs
@@ -600,6 +600,24 @@ namespace Avalonia.Base.UnitTests.Data.Core
result);
}
+ [Fact]
+ public void Should_Not_Throw_Exception_On_Duplicate_Properties()
+ {
+ // Repro of https://github.com/AvaloniaUI/Avalonia/issues/4733.
+ var source = new MyViewModel();
+ var target = new PropertyAccessorNode("Name", false);
+
+ target.Target = new WeakReference