[X] binding find the right indexer (#7896)

supports having multiple indexers on a bound source, try to invoke the right one

- fixes #7837
This commit is contained in:
Stephane Delcroix 2019-10-10 10:19:04 +02:00 коммит произвёл GitHub
Родитель 2e531d726e
Коммит c137d02ed0
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
6 изменённых файлов: 139 добавлений и 46 удалений

Просмотреть файл

@ -495,7 +495,22 @@ namespace Xamarin.Forms.Build.Tasks
if (indexArg != null) {
var defaultMemberAttribute = previousPartTypeRef.GetCustomAttribute(module, ("mscorlib", "System.Reflection", "DefaultMemberAttribute"));
var indexerName = defaultMemberAttribute?.ConstructorArguments?.FirstOrDefault().Value as string ?? "Item";
var indexer = previousPartTypeRef.GetProperty(pd => pd.Name == indexerName && pd.GetMethod != null && pd.GetMethod.IsPublic, out var indexerDeclTypeRef);
PropertyDefinition indexer = null;
TypeReference indexerDeclTypeRef = null;
if (int.TryParse(indexArg, out _))
indexer = previousPartTypeRef.GetProperty(pd => pd.Name == indexerName
&& pd.GetMethod != null
&& TypeRefComparer.Default.Equals(pd.GetMethod.Parameters[0].ParameterType, module.ImportReference(("mscorlib", "System", "Int32")))
&& pd.GetMethod.IsPublic, out indexerDeclTypeRef);
indexer = indexer ?? previousPartTypeRef.GetProperty(pd => pd.Name == indexerName
&& pd.GetMethod != null
&& TypeRefComparer.Default.Equals(pd.GetMethod.Parameters[0].ParameterType, module.ImportReference(("mscorlib", "System", "String")))
&& pd.GetMethod.IsPublic, out indexerDeclTypeRef);
indexer = indexer ?? previousPartTypeRef.GetProperty(pd => pd.Name == indexerName
&& pd.GetMethod != null
&& TypeRefComparer.Default.Equals(pd.GetMethod.Parameters[0].ParameterType, module.ImportReference(("mscorlib", "System", "String")))
&& pd.GetMethod.IsPublic, out indexerDeclTypeRef);
properties.Add((indexer, indexerDeclTypeRef, indexArg));
var indexType = indexer.GetMethod.Parameters[0].ParameterType.ResolveGenericParameters(indexerDeclTypeRef);
if (!TypeRefComparer.Default.Equals(indexType, module.TypeSystem.String) && !TypeRefComparer.Default.Equals(indexType, module.TypeSystem.Int32))

Просмотреть файл

@ -11,11 +11,9 @@ namespace Xamarin.Forms.Build.Tasks
{
static string GetAssembly(TypeReference typeRef)
{
var md = typeRef.Scope as ModuleDefinition;
if (md != null)
if (typeRef.Scope is ModuleDefinition md)
return md.Assembly.FullName;
var anr = typeRef.Scope as AssemblyNameReference;
if (anr != null)
if (typeRef.Scope is AssemblyNameReference anr)
return anr.FullName;
throw new ArgumentOutOfRangeException(nameof(typeRef));
}

Просмотреть файл

@ -243,6 +243,54 @@ namespace Xamarin.Forms
}
}
PropertyInfo GetIndexer(TypeInfo sourceType, string indexerName, string content)
{
if (int.TryParse(content, out _)) { //try to find an indexer taking an int
foreach (var pi in sourceType.DeclaredProperties) {
if (pi.Name != indexerName)
continue;
if (pi.CanRead && pi.GetMethod.GetParameters()[0].ParameterType == typeof(int))
return pi;
if (pi.CanWrite && pi.SetMethod.ReturnType == typeof(int))
return pi;
}
}
//property isn't an int, or there wasn't any int indexer
foreach (var pi in sourceType.DeclaredProperties) {
if (pi.Name != indexerName)
continue;
if (pi.CanRead && pi.GetMethod.GetParameters()[0].ParameterType == typeof(string))
return pi;
if (pi.CanWrite && pi.SetMethod.ReturnType == typeof(string))
return pi;
}
//try to fallback to an object indexer
foreach (var pi in sourceType.DeclaredProperties)
{
if (pi.Name != indexerName)
continue;
if (pi.CanRead && pi.GetMethod.GetParameters()[0].ParameterType == typeof(object))
return pi;
if (pi.CanWrite && pi.SetMethod.ReturnType == typeof(object))
return pi;
}
//defined on an interface ?
foreach (var face in sourceType.ImplementedInterfaces) {
if (GetIndexer(face.GetTypeInfo(), indexerName, content) is PropertyInfo pi)
return pi;
}
//defined on a base class ?
if (sourceType.BaseType is Type baseT && GetIndexer(baseT.GetTypeInfo(), indexerName, content) is PropertyInfo p)
return p;
return null;
}
void SetupPart(TypeInfo sourceType, BindingExpressionPart part)
{
part.Arguments = null;
@ -254,8 +302,7 @@ namespace Xamarin.Forms
{
if (sourceType.IsArray)
{
int index;
if (!int.TryParse(part.Content, out index))
if (!int.TryParse(part.Content, out var index))
Log.Warning("Binding", "{0} could not be parsed as an index for a {1}", part.Content, sourceType);
else
part.Arguments = new object[] { index };
@ -265,46 +312,16 @@ namespace Xamarin.Forms
part.SetterType = sourceType.GetElementType();
}
DefaultMemberAttribute defaultMember = null;
foreach (var attrib in sourceType.GetCustomAttributes(typeof(DefaultMemberAttribute), true))
string indexerName = "Item";
foreach (DefaultMemberAttribute attrib in sourceType.GetCustomAttributes(typeof(DefaultMemberAttribute), true))
{
if (attrib is DefaultMemberAttribute d)
{
defaultMember = d;
break;
}
indexerName = attrib.MemberName;
break;
}
string indexerName = defaultMember != null ? defaultMember.MemberName : "Item";
part.IndexerName = indexerName;
#if NETSTANDARD2_0
try {
property = sourceType.GetDeclaredProperty(indexerName);
}
catch (AmbiguousMatchException) {
// Get most derived instance of property
foreach (var p in sourceType.GetProperties()) {
if (p.Name == indexerName && (property == null || property.DeclaringType.IsAssignableFrom(property.DeclaringType)))
property = p;
}
}
#else
property = sourceType.GetDeclaredProperty(indexerName);
#endif
if (property == null) //is the indexer defined on the base class?
property = sourceType.BaseType.GetProperty(indexerName);
if (property == null) //is the indexer defined on implemented interface ?
{
foreach (var implementedInterface in sourceType.ImplementedInterfaces)
{
property = implementedInterface.GetProperty(indexerName);
if (property != null)
break;
}
}
property = GetIndexer(sourceType, indexerName, part.Content);
if (property != null)
{
@ -312,9 +329,7 @@ namespace Xamarin.Forms
ParameterInfo[] array = property.GetIndexParameters();
if (array.Length > 0)
{
parameter = array[0];
}
if (parameter != null)
{

Просмотреть файл

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="using:Xamarin.Forms.Xaml.UnitTests"
x:Class="Xamarin.Forms.Xaml.UnitTests.Gh7837">
<ContentPage.BindingContext>
<local:Gh7837VM />
</ContentPage.BindingContext>
<StackLayout>
<Label x:Name="label0" Text="{Binding .[42]}" />
<Label x:Name="label1" Text="{Binding .[foo]}" />
<Label x:Name="label2" Text="{Binding .[42]}" x:DataType="local:Gh7837VM" />
<Label x:Name="label3" Text="{Binding .[foo]}" x:DataType="local:Gh7837VM" />
</StackLayout>
</ContentPage>

Просмотреть файл

@ -0,0 +1,50 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using NUnit.Framework;
using Xamarin.Forms;
using Xamarin.Forms.Core.UnitTests;
namespace Xamarin.Forms.Xaml.UnitTests
{
public class Gh7837VMBase //using a base class to test #2131
{
public string this[int index] => "";
public string this[string index] => "";
}
public class Gh7837VM : Gh7837VMBase
{
public new string this[int index] => index == 42 ? "forty-two" : "dull number";
public new string this[string index] => index.ToUpper();
}
public partial class Gh7837 : ContentPage
{
public Gh7837() => InitializeComponent();
public Gh7837(bool useCompiledXaml)
{
//this stub will be replaced at compile time
}
[TestFixture]
class Tests
{
[SetUp] public void Setup() => Device.PlatformServices = new MockPlatformServices();
[TearDown] public void TearDown() => Device.PlatformServices = null;
[Test]
public void BindingWithMultipleIndexers([Values(false, true)]bool useCompiledXaml)
{
if (useCompiledXaml)
MockCompiler.Compile(typeof(Gh7837));
var layout = new Gh7837(useCompiledXaml);
Assert.That(layout.label0.Text, Is.EqualTo("forty-two"));
Assert.That(layout.label1.Text, Is.EqualTo("FOO"));
Assert.That(layout.label2.Text, Is.EqualTo("forty-two"));
Assert.That(layout.label3.Text, Is.EqualTo("FOO"));
}
}
}
}

Просмотреть файл

@ -35,8 +35,7 @@ namespace Xamarin.Forms.Xaml.UnitTests
BuildEngine = new MSBuild.UnitTests.DummyBuildEngine()
};
IList<Exception> exceptions;
if (xamlc.Execute(out exceptions) || exceptions == null || !exceptions.Any()) {
if (xamlc.Execute(out IList<Exception> exceptions) || exceptions == null || !exceptions.Any()) {
methdoDefinition = xamlc.InitCompForType;
return;
}