diff --git a/src/Controls/src/Core/BindingExpression.cs b/src/Controls/src/Core/BindingExpression.cs index 7cd740d0f705..aaf80c3fcd1e 100644 --- a/src/Controls/src/Core/BindingExpression.cs +++ b/src/Controls/src/Core/BindingExpression.cs @@ -318,6 +318,28 @@ PropertyInfo GetIndexer(TypeInfo sourceType, string indexerName, string content) return null; } + PropertyInfo GetProperty(TypeInfo sourceType, string propertyName) + { + // First, check the type and its base classes + TypeInfo type = sourceType; + do + { + var property = type.GetDeclaredProperty(propertyName); + if (property != null) + return property; + } while ((type = type.BaseType?.GetTypeInfo()) != null); + + // If not found, check implemented interfaces (for interface-inherited properties like IReadOnlyList.Count) + foreach (var iface in sourceType.ImplementedInterfaces) + { + var property = GetProperty(iface.GetTypeInfo(), propertyName); + if (property != null) + return property; + } + + return null; + } + void SetupPart(TypeInfo sourceType, BindingExpressionPart part) { @@ -382,11 +404,7 @@ void SetupPart(TypeInfo sourceType, BindingExpressionPart part) } else { - TypeInfo type = sourceType; - do - { - property = type.GetDeclaredProperty(part.Content); - } while (property == null && (type = type.BaseType?.GetTypeInfo()) != null); + property = GetProperty(sourceType, part.Content); } if (property != null) { diff --git a/src/Controls/tests/SourceGen.UnitTests/Maui32879Tests.cs b/src/Controls/tests/SourceGen.UnitTests/Maui32879Tests.cs index eb5e2c121aa7..e0443ceaf400 100644 --- a/src/Controls/tests/SourceGen.UnitTests/Maui32879Tests.cs +++ b/src/Controls/tests/SourceGen.UnitTests/Maui32879Tests.cs @@ -60,6 +60,7 @@ public TestPage() // //------------------------------------------------------------------------------ #nullable enable +#pragma warning disable CS0219 // Variable is assigned but its value is never used namespace Test; diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Maui13872.xaml b/src/Controls/tests/Xaml.UnitTests/Issues/Maui13872.xaml new file mode 100644 index 000000000000..27812cf22430 --- /dev/null +++ b/src/Controls/tests/Xaml.UnitTests/Issues/Maui13872.xaml @@ -0,0 +1,12 @@ + + + + + diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Maui13872.xaml.cs b/src/Controls/tests/Xaml.UnitTests/Issues/Maui13872.xaml.cs new file mode 100644 index 000000000000..335b1a5d1f88 --- /dev/null +++ b/src/Controls/tests/Xaml.UnitTests/Issues/Maui13872.xaml.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using NUnit.Framework; + +namespace Microsoft.Maui.Controls.Xaml.UnitTests; + +public partial class Maui13872 : ContentPage +{ + public Maui13872() => InitializeComponent(); + + [TestFixture] + class Tests + { + [Test] + public void CompiledBindingToIReadOnlyListCount([Values] XamlInflator inflator) + { + var page = new Maui13872(inflator); + page.BindingContext = new Maui13872ViewModel(); + + // Uncompiled bindings (no x:DataType) - should work with all inflators + Assert.That(page.label0.Text, Is.EqualTo("3"), "Uncompiled binding to List.Count"); + Assert.That(page.label1.Text, Is.EqualTo("3"), "Uncompiled binding to ListCount"); + + // Compiled bindings (with x:DataType) - IReadOnlyList.Count should resolve correctly. + // Count is defined on IReadOnlyCollection which IReadOnlyList inherits. + Assert.That(page.label2.Text, Is.EqualTo("3"), "Compiled binding to List.Count"); + Assert.That(page.label3.Text, Is.EqualTo("3"), "Compiled binding to ListCount"); + } + } +} + +public class Maui13872ViewModel +{ + private readonly string[] _list = ["Bill", "Steve", "John"]; + + public IReadOnlyList List => _list; + + public int ListCount => _list.Length; +}