From e7afc15ca7e40229e5bbacabeb27c601a5db585f Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Sat, 18 Dec 2021 11:36:52 -0600 Subject: [PATCH] Implement the runtime side of the "method->Delegate" binding conversion for compiled bindings. --- .../Data/Core/Plugins/MethodAccessorPlugin.cs | 26 ++----- .../Avalonia.Markup.Xaml.csproj | 1 + .../CompiledBindings/CompiledBindingPath.cs | 23 ++++++ .../CompiledBindings/MethodAccessorPlugin.cs | 74 +++++++++++++++++++ 4 files changed, 106 insertions(+), 18 deletions(-) create mode 100644 src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/MethodAccessorPlugin.cs diff --git a/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs index 160c7301f5..f4e5ab3dbc 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs @@ -22,19 +22,9 @@ namespace Avalonia.Data.Core.Plugins var method = GetFirstMethodWithName(instance.GetType(), methodName); - if (method != null) + if (method is not null) { - var parameters = method.GetParameters(); - - if (parameters.Length + (method.ReturnType == typeof(void) ? 0 : 1) > 8) - { - var exception = new ArgumentException( - "Cannot create a binding accessor for a method with more than 8 parameters or more than 7 parameters if it has a non-void return type.", - nameof(methodName)); - return new PropertyError(new BindingNotification(exception, BindingErrorType.Error)); - } - - return new Accessor(reference, method, parameters); + return new Accessor(reference, method); } else { @@ -82,7 +72,7 @@ namespace Avalonia.Data.Core.Plugins private sealed class Accessor : PropertyAccessorBase { - public Accessor(WeakReference reference, MethodInfo method, ParameterInfo[] parameters) + public Accessor(WeakReference reference, MethodInfo method) { _ = reference ?? throw new ArgumentNullException(nameof(reference)); _ = method ?? throw new ArgumentNullException(nameof(method)); @@ -90,10 +80,13 @@ namespace Avalonia.Data.Core.Plugins var returnType = method.ReturnType; bool hasReturn = returnType != typeof(void); + var parameters = method.GetParameters(); + var signatureTypeCount = (hasReturn ? 1 : 0) + parameters.Length; var paramTypes = new Type[signatureTypeCount]; + for (var i = 0; i < parameters.Length; i++) { ParameterInfo parameter = parameters[i]; @@ -105,13 +98,10 @@ namespace Avalonia.Data.Core.Plugins { paramTypes[paramTypes.Length - 1] = returnType; - PropertyType = Expression.GetFuncType(paramTypes); - } - else - { - PropertyType = Expression.GetActionType(paramTypes); } + PropertyType = Expression.GetDelegateType(paramTypes); + if (method.IsStatic) { Value = method.CreateDelegate(PropertyType); diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index ac6aced8cb..d44a067882 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -20,6 +20,7 @@ + diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs index 11489c39aa..3e468d8c8e 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using Avalonia.Controls; using Avalonia.Data.Core; using Avalonia.Data.Core.Plugins; @@ -36,6 +37,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings case PropertyElement prop: node = new PropertyAccessorNode(prop.Property.Name, enableValidation, new PropertyInfoAccessorPlugin(prop.Property, prop.AccessorFactory)); break; + case MethodAsDelegateElement methodAsDelegate: + node = new PropertyAccessorNode(methodAsDelegate.Method.Name, enableValidation, new MethodAccessorPlugin(methodAsDelegate.Method, methodAsDelegate.DelegateType)); + break; case ArrayElementPathElement arr: node = new PropertyAccessorNode(CommonPropertyNames.IndexerName, enableValidation, new ArrayElementPlugin(arr.Indices, arr.ElementType)); break; @@ -92,6 +96,12 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings return this; } + public CompiledBindingPathBuilder Method(RuntimeMethodHandle handle, Type delegateType) + { + _elements.Add(new MethodAsDelegateElement(handle, delegateType)); + return this; + } + public CompiledBindingPathBuilder StreamTask() { _elements.Add(new TaskStreamPathElement()); @@ -178,6 +188,19 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings => _isFirstElement ? Property.Name : $".{Property.Name}"; } + internal class MethodAsDelegateElement : ICompiledBindingPathElement + { + public MethodAsDelegateElement(RuntimeMethodHandle method, Type delegateType) + { + Method = (MethodInfo)MethodBase.GetMethodFromHandle(method); + DelegateType = delegateType; + } + + public MethodInfo Method { get; } + + public Type DelegateType { get; } + } + internal interface IStronglyTypedStreamElement : ICompiledBindingPathElement { IStreamPlugin CreatePlugin(); diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/MethodAccessorPlugin.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/MethodAccessorPlugin.cs new file mode 100644 index 0000000000..45ad45e658 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/MethodAccessorPlugin.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using System.Text; +using Avalonia.Data; +using Avalonia.Data.Core.Plugins; + +#nullable enable + +namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings +{ + internal class MethodAccessorPlugin : IPropertyAccessorPlugin + { + private MethodInfo _method; + private readonly Type _delegateType; + + public MethodAccessorPlugin(MethodInfo method, Type delegateType) + { + _method = method; + _delegateType = delegateType; + } + + public bool Match(object obj, string propertyName) + { + throw new InvalidOperationException("The MethodAccessorPlugin does not support dynamic matching"); + } + + public IPropertyAccessor Start(WeakReference reference, string propertyName) + { + Debug.Assert(_method.Name == propertyName); + return new Accessor(reference, _method, _delegateType); + } + + private sealed class Accessor : PropertyAccessorBase + { + public Accessor(WeakReference reference, MethodInfo method, Type delegateType) + { + _ = reference ?? throw new ArgumentNullException(nameof(reference)); + _ = method ?? throw new ArgumentNullException(nameof(method)); + + PropertyType = delegateType; + + if (method.IsStatic) + { + Value = method.CreateDelegate(PropertyType); + } + else if (reference.TryGetTarget(out var target)) + { + Value = method.CreateDelegate(PropertyType, target); + } + } + + public override Type? PropertyType { get; } + + public override object? Value { get; } + + public override bool SetValue(object? value, BindingPriority priority) => false; + + protected override void SubscribeCore() + { + try + { + PublishValue(Value); + } + catch { } + } + + protected override void UnsubscribeCore() + { + } + } + } +}