[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:
Родитель
2e531d726e
Коммит
c137d02ed0
|
@ -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;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче