ソースを参照

Add failing tests for #4733 and fix property accessor.

Dariusz Komosiński 5 年 前
コミット
8555fed86b

+ 38 - 6
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
     /// </summary>
     public class InpcPropertyAccessorPlugin : IPropertyAccessorPlugin
     {
+        private readonly Dictionary<(Type, string), PropertyInfo> _propertyLookup =
+            new Dictionary<(Type, string), PropertyInfo>();
+        
         /// <inheritdoc/>
-        public bool Match(object obj, string propertyName) => GetPropertyWithName(obj.GetType(), propertyName) != null;
+        public bool Match(object obj, string propertyName) => GetFirstPropertyWithName(obj.GetType(), propertyName) != null;
 
         /// <summary>
         /// 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<PropertyChangedEventArgs>

+ 29 - 0
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<object>(source);
+            
+            var result = new List<object>();
+            
+            target.Subscribe(x => result.Add(x));
+        }
+        
+        public class MyViewModelBase { public object Name => "Name"; }
+        
+        public class MyViewModel : MyViewModelBase { public new string Name => "NewName"; }
+
         private interface INext
         {
             int PropertyChangedSubscriptionCount { get; }
@@ -664,6 +682,17 @@ namespace Avalonia.Base.UnitTests.Data.Core
             }
         }
 
+        private class WithNewBar : Class1
+        {
+            private string _bar;
+            
+            public new string Bar
+            {
+                get { return _bar; }
+                set { _bar = value; }
+            }
+        }
+
         private class Class3 : Class1
         {
         }