diff --git a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
index 33cecd10a7..b93bf87fdf 100644
--- a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
+++ b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
@@ -17,7 +17,7 @@ namespace Avalonia.Data.Core.Plugins
new Dictionary<(Type, string), PropertyInfo?>();
///
- public bool Match(object obj, string propertyName) => GetFirstPropertyWithName(obj.GetType(), propertyName) != null;
+ public bool Match(object obj, string propertyName) => GetFirstPropertyWithName(obj, propertyName) != null;
///
/// Starts monitoring the value of a property on an object.
@@ -36,7 +36,7 @@ namespace Avalonia.Data.Core.Plugins
if (!reference.TryGetTarget(out var instance) || instance is null)
return null;
- var p = GetFirstPropertyWithName(instance.GetType(), propertyName);
+ var p = GetFirstPropertyWithName(instance, propertyName);
if (p != null)
{
@@ -50,8 +50,16 @@ namespace Avalonia.Data.Core.Plugins
}
}
- private PropertyInfo? GetFirstPropertyWithName(Type type, string propertyName)
+ private const BindingFlags PropertyBindingFlags =
+ BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance;
+
+ private PropertyInfo? GetFirstPropertyWithName(object instance, string propertyName)
{
+ if (instance is IReflectableType reflectableType)
+ return reflectableType.GetTypeInfo().GetProperty(propertyName, PropertyBindingFlags);
+
+ var type = instance.GetType();
+
var key = (type, propertyName);
if (!_propertyLookup.TryGetValue(key, out var propertyInfo))
@@ -66,10 +74,7 @@ namespace Avalonia.Data.Core.Plugins
{
PropertyInfo? found = null;
- const BindingFlags bindingFlags =
- BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance;
-
- var properties = type.GetProperties(bindingFlags);
+ var properties = type.GetProperties(PropertyBindingFlags);
foreach (PropertyInfo propertyInfo in properties)
{
diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs
index 055de999e2..11a22f0dec 100644
--- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs
+++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs
@@ -617,6 +617,25 @@ namespace Avalonia.Markup.UnitTests.Data
Assert.Equal(0, source.SubscriberCount);
}
+
+ [Fact]
+ public void Binding_Can_Resolve_Property_From_IReflectableType_Type()
+ {
+ var source = new DynamicReflectableType { ["Foo"] = "foo" };
+ var target = new TwoWayBindingTest { DataContext = source };
+ var binding = new Binding
+ {
+ Path = "Foo",
+ };
+
+ target.Bind(TwoWayBindingTest.TwoWayProperty, binding);
+
+ Assert.Equal("foo", target.TwoWay);
+ source["Foo"] = "bar";
+ Assert.Equal("bar", target.TwoWay);
+ target.TwoWay = "baz";
+ Assert.Equal("baz", source["Foo"]);
+ }
private class StyledPropertyClass : AvaloniaObject
{
diff --git a/tests/Avalonia.Markup.UnitTests/Data/DynamicReflectableType.cs b/tests/Avalonia.Markup.UnitTests/Data/DynamicReflectableType.cs
new file mode 100644
index 0000000000..3c57bd38cf
--- /dev/null
+++ b/tests/Avalonia.Markup.UnitTests/Data/DynamicReflectableType.cs
@@ -0,0 +1,221 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Globalization;
+using System.Reflection;
+using Moq;
+
+namespace Avalonia.Markup.UnitTests.Data;
+
+class DynamicReflectableType : IReflectableType, INotifyPropertyChanged, IEnumerable>
+{
+ private Dictionary _dic = new();
+
+ public TypeInfo GetTypeInfo()
+ {
+ return new FakeTypeInfo();
+ }
+
+ public void Add(string key, object value)
+ {
+ _dic.Add(key, value);
+
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(key));
+ }
+
+ public object this[string key]
+ {
+ get => _dic[key];
+ set
+ {
+ _dic[key] = value;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(key));
+ }
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+ public IEnumerator> GetEnumerator()
+ {
+ return _dic.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return ((IEnumerable)_dic).GetEnumerator();
+ }
+
+
+ class FakeTypeInfo : TypeInfo
+ {
+ protected override PropertyInfo GetPropertyImpl(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types,
+ ParameterModifier[] modifiers)
+ {
+ var propInfo = new Mock();
+ propInfo.SetupGet(x => x.Name).Returns(name);
+ propInfo.SetupGet(x => x.PropertyType).Returns(typeof(object));
+ propInfo.SetupGet(x => x.CanWrite).Returns(true);
+ propInfo.Setup(x => x.GetValue(It.IsAny