diff --git a/src/Controls/src/Core/BindableProperty.cs b/src/Controls/src/Core/BindableProperty.cs index 89f4660f4b2b..eec79e31de45 100644 --- a/src/Controls/src/Core/BindableProperty.cs +++ b/src/Controls/src/Core/BindableProperty.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; @@ -240,5 +241,45 @@ internal bool TryConvert(ref object value) } internal delegate void BindablePropertyBindingChanging(BindableObject bindable, BindingBase oldValue, BindingBase newValue); + + static readonly ConcurrentDictionary s_converterCache = new(); + + internal TypeConverter GetBindablePropertyTypeConverter() + => GetBindablePropertyTypeConverter(isAttached: null); + + internal TypeConverter GetBindablePropertyTypeConverter(bool? isAttached) + { + if (s_converterCache.TryGetValue(this, out var converter)) + return converter; + + Type converterType = null; + if (!isAttached.HasValue || !isAttached.Value) + try + { + converterType = ((MemberInfo)DeclaringType.GetRuntimeProperty(PropertyName))?.GetTypeConverterType(); + } + catch (AmbiguousMatchException e) //GetRuntimeProperty can throw, we'd like to catch that and rephrase + { + throw new AmbiguousMatchException($"Multiple properties with name '{DeclaringType}.{PropertyName}' found.", e); + } + + if ((!isAttached.HasValue && converterType == null) || (isAttached.HasValue && isAttached.Value)) + try + { + converterType = (DeclaringType.GetRuntimeMethod("Get" + PropertyName, new[] { typeof(BindableObject) }))?.GetTypeConverterType(); + } + catch (AmbiguousMatchException e) //GetRuntimeProperty can throw, we'd like to catch that and rephrase + { + throw new AmbiguousMatchException($"Multiple properties with name '{DeclaringType}.Get{PropertyName}' found.", e); + } + + if (converterType == null) + converterType = ReturnType.GetTypeConverterType(); + + converter = converterType == null ? null : (TypeConverter)Activator.CreateInstance(converterType); + + // cache the result, even if it is null + return (s_converterCache[this] = converter); + } } } diff --git a/src/Controls/src/Core/IValueConverterProvider.cs b/src/Controls/src/Core/IValueConverterProvider.cs index d78ff74125da..c7a2223ba713 100644 --- a/src/Controls/src/Core/IValueConverterProvider.cs +++ b/src/Controls/src/Core/IValueConverterProvider.cs @@ -1,10 +1,11 @@ using System; +using System.ComponentModel; using System.Reflection; namespace Microsoft.Maui.Controls.Xaml { interface IValueConverterProvider { - object Convert(object value, Type toType, Func minfoRetriever, IServiceProvider serviceProvider); + object Convert(object value, Type toType, Func getTypeConverter, IServiceProvider serviceProvider); } } \ No newline at end of file diff --git a/src/Controls/src/Core/Interactivity/BindingCondition.cs b/src/Controls/src/Core/Interactivity/BindingCondition.cs index 53aa5a2aa1a5..79261e8703e5 100644 --- a/src/Controls/src/Core/Interactivity/BindingCondition.cs +++ b/src/Controls/src/Core/Interactivity/BindingCondition.cs @@ -79,8 +79,12 @@ bool EqualsToValue(object other) return true; object converted = null; + if (s_valueConverter != null) - converted = s_valueConverter.Convert(Value, other != null ? other.GetType() : typeof(object), null, null); + converted = s_valueConverter.Convert(Value, + other != null ? other.GetType() : typeof(object), + other != null ? other.GetType().GetTypeConverter : null, + null); else return false; diff --git a/src/Controls/src/Core/Interactivity/PropertyCondition.cs b/src/Controls/src/Core/Interactivity/PropertyCondition.cs index d709645e9dba..c133fad65350 100644 --- a/src/Controls/src/Core/Interactivity/PropertyCondition.cs +++ b/src/Controls/src/Core/Interactivity/PropertyCondition.cs @@ -6,9 +6,7 @@ namespace Microsoft.Maui.Controls { /// - [ProvideCompiled("Microsoft.Maui.Controls.XamlC.PassthroughValueProvider")] - [AcceptEmptyServiceProvider] - public sealed class PropertyCondition : Condition, IValueProvider + public sealed class PropertyCondition : Condition { readonly BindableProperty _stateProperty; @@ -24,7 +22,7 @@ public PropertyCondition() /// public BindableProperty Property { - get { return _property; } + get => _property; set { if (_property == value) @@ -35,27 +33,14 @@ public BindableProperty Property //convert the value if (_property != null && s_valueConverter != null) - { - Func minforetriever = () => - { - try - { - return Property.DeclaringType.GetRuntimeProperty(Property.PropertyName); - } - catch (AmbiguousMatchException e) - { - throw new XamlParseException($"Multiple properties with name '{Property.DeclaringType}.{Property.PropertyName}' found.", new XmlLineInfo(), innerException: e); - } - }; - Value = s_valueConverter.Convert(Value, Property.ReturnType, minforetriever, null); - } + _triggerValue = s_valueConverter.Convert(Value, _property.ReturnType, _property.GetBindablePropertyTypeConverter, null); } } /// public object Value { - get { return _triggerValue; } + get => _triggerValue; set { if (_triggerValue == value) @@ -65,30 +50,12 @@ public object Value //convert the value if (_property != null && s_valueConverter != null) - { - Func minforetriever = () => - { - try - { - return Property.DeclaringType.GetRuntimeProperty(Property.PropertyName); - } - catch (AmbiguousMatchException e) - { - throw new XamlParseException($"Multiple properties with name '{Property.DeclaringType}.{Property.PropertyName}' found.", new XmlLineInfo(), innerException: e); - } - }; - value = s_valueConverter.Convert(value, Property.ReturnType, minforetriever, null); - } - _triggerValue = value; + _triggerValue = s_valueConverter.Convert(value, _property.ReturnType, _property.GetBindablePropertyTypeConverter, null); + else + _triggerValue = value; } } - object IValueProvider.ProvideValue(IServiceProvider serviceProvider) - { - //This is no longer required - return this; - } - internal override bool GetState(BindableObject bindable) { return (bool)bindable.GetValue(_stateProperty); diff --git a/src/Controls/src/Core/OnPlatform.cs b/src/Controls/src/Core/OnPlatform.cs index edf4dbb37c0d..b59bafcde945 100644 --- a/src/Controls/src/Core/OnPlatform.cs +++ b/src/Controls/src/Core/OnPlatform.cs @@ -40,14 +40,14 @@ public static implicit operator T(OnPlatform onPlatform) continue; if (!onPlat.Platform.Contains(DeviceInfo.Platform.ToString())) continue; - return (T)s_valueConverter.Convert(onPlat.Value, typeof(T), null, null); + return (T)s_valueConverter.Convert(onPlat.Value, typeof(T), typeof(T).GetTypeConverter, null); } // fallback for UWP foreach (var onPlat in onPlatform.Platforms) { if (onPlat.Platform != null && onPlat.Platform.Contains("UWP") && DeviceInfo.Platform == DevicePlatform.WinUI) - return (T)s_valueConverter.Convert(onPlat.Value, typeof(T), null, null); + return (T)s_valueConverter.Convert(onPlat.Value, typeof(T), typeof(T).GetTypeConverter, null); } } diff --git a/src/Controls/src/Core/Setter.cs b/src/Controls/src/Core/Setter.cs index d695b458c6da..d2dbba27613a 100644 --- a/src/Controls/src/Core/Setter.cs +++ b/src/Controls/src/Core/Setter.cs @@ -29,31 +29,8 @@ object IValueProvider.ProvideValue(IServiceProvider serviceProvider) throw new XamlParseException("Property not set", serviceProvider); var valueconverter = serviceProvider.GetService(typeof(IValueConverterProvider)) as IValueConverterProvider; - MemberInfo minforetriever() - { - MemberInfo minfo = null; - try - { - minfo = Property.DeclaringType.GetRuntimeProperty(Property.PropertyName); - } - catch (AmbiguousMatchException e) - { - throw new XamlParseException($"Multiple properties with name '{Property.DeclaringType}.{Property.PropertyName}' found.", serviceProvider, innerException: e); - } - if (minfo != null) - return minfo; - try - { - return Property.DeclaringType.GetRuntimeMethod("Get" + Property.PropertyName, new[] { typeof(BindableObject) }); - } - catch (AmbiguousMatchException e) - { - throw new XamlParseException($"Multiple methods with name '{Property.DeclaringType}.Get{Property.PropertyName}' found.", serviceProvider, innerException: e); - } - } + Value = valueconverter.Convert(Value, Property.ReturnType, Property.GetBindablePropertyTypeConverter, serviceProvider); - object value = valueconverter.Convert(Value, Property.ReturnType, minforetriever, serviceProvider); - Value = value; return this; } diff --git a/src/Controls/src/Core/StyleSheets/Style.cs b/src/Controls/src/Core/StyleSheets/Style.cs index fa36d9e9488c..7a49f470d245 100644 --- a/src/Controls/src/Core/StyleSheets/Style.cs +++ b/src/Controls/src/Core/StyleSheets/Style.cs @@ -92,33 +92,10 @@ public void Apply(VisualElement styleable, bool inheriting = false) [MethodImpl(MethodImplOptions.AggressiveInlining)] static object Convert(object target, object value, BindableProperty property) { - var serviceProvider = new StyleSheetServiceProvider(target, property); - Func minforetriever = - () => - { - MemberInfo minfo = null; - try - { - minfo = property.DeclaringType.GetRuntimeProperty(property.PropertyName); - } - catch (AmbiguousMatchException e) - { - throw new XamlParseException($"Multiple properties with name '{property.DeclaringType}.{property.PropertyName}' found.", serviceProvider, innerException: e); - } - if (minfo != null) - return minfo; - try - { - return property.DeclaringType.GetRuntimeMethod("Get" + property.PropertyName, new[] { typeof(BindableObject) }); - } - catch (AmbiguousMatchException e) - { - throw new XamlParseException($"Multiple methods with name '{property.DeclaringType}.Get{property.PropertyName}' found.", serviceProvider, innerException: e); - } - }; - var ret = value.ConvertTo(property.ReturnType, minforetriever, serviceProvider, out Exception exception); + var ret = value.ConvertTo(property.ReturnType, property.GetBindablePropertyTypeConverter, new StyleSheetServiceProvider(target, property), out var exception); if (exception != null) throw exception; + return ret; } diff --git a/src/Controls/src/Core/Xaml/TypeConversionExtensions.cs b/src/Controls/src/Core/Xaml/TypeConversionExtensions.cs index 5b4f6423c3de..1d0ed6795ff5 100644 --- a/src/Controls/src/Core/Xaml/TypeConversionExtensions.cs +++ b/src/Controls/src/Core/Xaml/TypeConversionExtensions.cs @@ -49,7 +49,7 @@ internal static object ConvertTo(this object value, Type toType, Func attributes) + internal static TypeConverter GetTypeConverter(this MemberInfo memberInfo) { - foreach (var converterAttribute in attributes) + if (s_converterCache.TryGetValue(memberInfo, out var converter)) + return converter; + + var converterType = memberInfo.GetTypeConverterType(); + if (converterType == null && memberInfo is PropertyInfo propertyInfo) + converterType = propertyInfo.PropertyType.GetTypeConverterType(); + + converter = converterType == null ? null : (TypeConverter)Activator.CreateInstance(converterType); + + // cache the result, even if it is null + return (s_converterCache[memberInfo] = converter); + } + + internal static Type GetTypeConverterType(this MemberInfo memberInfo) + { + if (memberInfo == null) + return null; + + var attributes = memberInfo.CustomAttributes; + if (attributes == null) + return null; + + foreach (var attribute in attributes) { - if (converterAttribute.AttributeType != typeof(System.ComponentModel.TypeConverterAttribute)) + if (attribute.AttributeType != typeof(System.ComponentModel.TypeConverterAttribute)) continue; - var ctor = converterAttribute.ConstructorArguments[0]; + var ctor = attribute.ConstructorArguments[0]; if (ctor.ArgumentType == typeof(string)) return Type.GetType((string)ctor.Value); if (ctor.ArgumentType == typeof(Type)) return (Type)ctor.Value; } + + return null; + } + + internal static Type GetTypeConverterType(this ParameterInfo parameterInfo) + { + if (parameterInfo == null) + return null; + + var attributes = parameterInfo.CustomAttributes; + if (attributes == null) + return null; + + foreach (var attribute in attributes) + { + if (attribute.AttributeType != typeof(System.ComponentModel.TypeConverterAttribute)) + continue; + var ctor = attribute.ConstructorArguments[0]; + if (ctor.ArgumentType == typeof(string)) + return Type.GetType((string)ctor.Value); + if (ctor.ArgumentType == typeof(Type)) + return (Type)ctor.Value; + } + return null; } @@ -155,7 +199,7 @@ internal static object ConvertTo(this object value, Type toType, Func minfoRetriever, IServiceProvider serviceProvider) + public object Convert(object value, Type toType, Func getTypeConverter, IServiceProvider serviceProvider) { - var ret = value.ConvertTo(toType, minfoRetriever, serviceProvider, out Exception exception); + var ret = value.ConvertTo(toType, getTypeConverter, serviceProvider, out Exception exception); if (exception != null) { var lineInfo = (serviceProvider.GetService(typeof(IXmlLineInfoProvider)) is IXmlLineInfoProvider lineInfoProvider) ? lineInfoProvider.XmlLineInfo : new XmlLineInfo(); diff --git a/src/Controls/src/Xaml/ApplyPropertiesVisitor.cs b/src/Controls/src/Xaml/ApplyPropertiesVisitor.cs index 89d0b88f5330..7866c476a073 100644 --- a/src/Controls/src/Xaml/ApplyPropertiesVisitor.cs +++ b/src/Controls/src/Xaml/ApplyPropertiesVisitor.cs @@ -565,32 +565,7 @@ static bool TrySetValue(object element, BindableProperty property, bool attached if (serviceProvider?.GetService() is XamlValueTargetProvider valueTargetProvider) valueTargetProvider.TargetProperty = property; - Func minforetriever; - if (attached) - minforetriever = () => - { - try - { - return property.DeclaringType.GetRuntimeMethod("Get" + property.PropertyName, new[] { typeof(BindableObject) }); - } - catch (AmbiguousMatchException e) - { - throw new XamlParseException($"Multiple methods with name '{property.DeclaringType}.Get{property.PropertyName}' found.", lineInfo, innerException: e); - } - }; - else - minforetriever = () => - { - try - { - return property.DeclaringType.GetRuntimeProperty(property.PropertyName); - } - catch (AmbiguousMatchException e) - { - throw new XamlParseException($"Multiple properties with name '{property.DeclaringType}.{property.PropertyName}' found.", lineInfo, innerException: e); - } - }; - var convertedValue = value.ConvertTo(property.ReturnType, minforetriever, serviceProvider, out exception); + var convertedValue = value.ConvertTo(property.ReturnType, () => property.GetBindablePropertyTypeConverter(isAttached: attached), serviceProvider, out exception); if (exception != null) return false; diff --git a/src/Controls/src/Xaml/MarkupExtensions/AppThemeBindingExtension.cs b/src/Controls/src/Xaml/MarkupExtensions/AppThemeBindingExtension.cs index 3a37e23a734f..daccd9f7d958 100644 --- a/src/Controls/src/Xaml/MarkupExtensions/AppThemeBindingExtension.cs +++ b/src/Controls/src/Xaml/MarkupExtensions/AppThemeBindingExtension.cs @@ -45,7 +45,7 @@ public object Dark BindingBase IMarkupExtension.ProvideValue(IServiceProvider serviceProvider) { - if (Default == null + if ( Default == null && Light == null && Dark == null) throw new XamlParseException("AppThemeBindingExtension requires a non-null value to be specified for at least one theme or Default.", serviceProvider); @@ -53,71 +53,28 @@ BindingBase IMarkupExtension.ProvideValue(IServiceProvider serviceP var valueProvider = serviceProvider?.GetService() ?? throw new ArgumentException(); BindableProperty bp; - PropertyInfo pi = null; Type propertyType = null; if (valueProvider.TargetObject is Setter setter) bp = setter.Property; - else - { + else bp = valueProvider.TargetProperty as BindableProperty; - pi = valueProvider.TargetProperty as PropertyInfo; - } - propertyType = bp?.ReturnType - ?? pi?.PropertyType - ?? throw new InvalidOperationException("Cannot determine property to provide the value for."); - - var converterProvider = serviceProvider?.GetService(); - - MemberInfo minforetriever() - { - if (pi != null) - return pi; - - MemberInfo minfo = null; - try - { - minfo = bp.DeclaringType.GetRuntimeProperty(bp.PropertyName); - } - catch (AmbiguousMatchException e) - { - throw new XamlParseException($"Multiple properties with name '{bp.DeclaringType}.{bp.PropertyName}' found.", serviceProvider, innerException: e); - } - if (minfo != null) - return minfo; - try - { - return bp.DeclaringType.GetRuntimeMethod("Get" + bp.PropertyName, new[] { typeof(BindableObject) }); - } - catch (AmbiguousMatchException e) - { - throw new XamlParseException($"Multiple methods with name '{bp.DeclaringType}.Get{bp.PropertyName}' found.", serviceProvider, innerException: e); - } - } var binding = new AppThemeBinding(); - if (converterProvider != null) - { - if (_haslight) - binding.Light = converterProvider.Convert(Light, propertyType, minforetriever, serviceProvider); - if (_hasdark) - binding.Dark = converterProvider.Convert(Dark, propertyType, minforetriever, serviceProvider); - if (_hasdefault) - binding.Default = converterProvider.Convert(Default, propertyType, minforetriever, serviceProvider); - return binding; - } Exception converterException = null; - - if (converterException != null && _haslight) - binding.Light = Light.ConvertTo(propertyType, minforetriever, serviceProvider, out converterException); - if (converterException != null && _hasdark) - binding.Dark = Dark.ConvertTo(propertyType, minforetriever, serviceProvider, out converterException); - if (converterException != null && _hasdefault) - binding.Default = Default.ConvertTo(propertyType, minforetriever, serviceProvider, out converterException); + if (converterException == null && _haslight) + binding.Light = Light.ConvertTo(propertyType, bp.GetBindablePropertyTypeConverter, serviceProvider, out converterException); + if (converterException == null && _hasdark) + binding.Dark = Dark.ConvertTo(propertyType, bp.GetBindablePropertyTypeConverter, serviceProvider, out converterException); + if (converterException == null && _hasdefault) + binding.Default = Default.ConvertTo(propertyType, bp.GetBindablePropertyTypeConverter, serviceProvider, out converterException); if (converterException != null) - throw converterException; + { + var lineInfo = (serviceProvider.GetService(typeof(IXmlLineInfoProvider)) is IXmlLineInfoProvider lineInfoProvider) ? lineInfoProvider.XmlLineInfo : new XmlLineInfo(); + throw new XamlParseException(converterException.Message, serviceProvider, converterException); + } return binding; } diff --git a/src/Controls/src/Xaml/MarkupExtensions/OnIdiomExtension.cs b/src/Controls/src/Xaml/MarkupExtensions/OnIdiomExtension.cs index a70921a84e7d..474c511ff9c0 100644 --- a/src/Controls/src/Xaml/MarkupExtensions/OnIdiomExtension.cs +++ b/src/Controls/src/Xaml/MarkupExtensions/OnIdiomExtension.cs @@ -62,38 +62,13 @@ public object ProvideValue(IServiceProvider serviceProvider) var converterProvider = serviceProvider?.GetService(); if (converterProvider != null) { - MemberInfo minforetriever() - { - if (pi != null) - return pi; + if (pi != null) + return converterProvider.Convert(value, propertyType, pi.GetTypeConverter, serviceProvider); - MemberInfo minfo = null; - try - { - minfo = bp.DeclaringType.GetRuntimeProperty(bp.PropertyName); - } - catch (AmbiguousMatchException e) - { - throw new XamlParseException($"Multiple properties with name '{bp.DeclaringType}.{bp.PropertyName}' found.", serviceProvider, innerException: e); - } - if (minfo != null) - return minfo; - try - { - return bp.DeclaringType.GetRuntimeMethod("Get" + bp.PropertyName, new[] { typeof(BindableObject) }); - } - catch (AmbiguousMatchException e) - { - throw new XamlParseException($"Multiple methods with name '{bp.DeclaringType}.Get{bp.PropertyName}' found.", serviceProvider, innerException: e); - } - } - - return converterProvider.Convert(value, propertyType, minforetriever, serviceProvider); + return converterProvider.Convert(value, propertyType, bp.GetBindablePropertyTypeConverter, serviceProvider); } - if (converterProvider != null) - return converterProvider.Convert(value, propertyType, () => pi, serviceProvider); - var ret = value.ConvertTo(propertyType, () => pi, serviceProvider, out Exception exception); + var ret = value.ConvertTo(propertyType, pi.GetTypeConverter, serviceProvider, out Exception exception); if (exception != null) throw exception; return ret; diff --git a/src/Controls/src/Xaml/MarkupExtensions/OnPlatformExtension.cs b/src/Controls/src/Xaml/MarkupExtensions/OnPlatformExtension.cs index a01e80d67a5a..529f5d00729d 100644 --- a/src/Controls/src/Xaml/MarkupExtensions/OnPlatformExtension.cs +++ b/src/Controls/src/Xaml/MarkupExtensions/OnPlatformExtension.cs @@ -77,35 +77,12 @@ public object ProvideValue(IServiceProvider serviceProvider) var converterProvider = serviceProvider?.GetService(); if (converterProvider != null) { - MemberInfo minforetriever() - { - if (pi != null) - return pi; - MemberInfo minfo = null; - try - { - minfo = bp.DeclaringType.GetRuntimeProperty(bp.PropertyName); - } - catch (AmbiguousMatchException e) - { - throw new XamlParseException($"Multiple properties with name '{bp.DeclaringType}.{bp.PropertyName}' found.", serviceProvider, innerException: e); - } - if (minfo != null) - return minfo; - try - { - return bp.DeclaringType.GetRuntimeMethod("Get" + bp.PropertyName, new[] { typeof(BindableObject) }); - } - catch (AmbiguousMatchException e) - { - throw new XamlParseException($"Multiple methods with name '{bp.DeclaringType}.Get{bp.PropertyName}' found.", serviceProvider, innerException: e); - } - } - - return converterProvider.Convert(value, propertyType, minforetriever, serviceProvider); + if (pi != null) + return converterProvider.Convert(value, propertyType, pi.GetTypeConverter, serviceProvider); + return converterProvider.Convert(value, propertyType, bp.GetBindablePropertyTypeConverter, serviceProvider); } - var ret = value.ConvertTo(propertyType, () => pi, serviceProvider, out Exception exception); + var ret = value.ConvertTo(propertyType, pi.GetTypeConverter, serviceProvider, out Exception exception); if (exception != null) throw exception; return ret;