From 1d5e26248710989340bc9bbafd4e4371bae25ca7 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 22 Dec 2021 16:24:08 -0600 Subject: [PATCH] Use a dynamically-emitted trampoline method so we can handle the "execute method has a return type" case cleanly. This also provides a location for us to add any other trampolines that we need to emit. --- .../XamlCompilerTaskExecutor.cs | 5 +- .../AvaloniaXamlIlRuntimeCompiler.cs | 5 +- .../AvaloniaXamlIlCompilerConfiguration.cs | 4 + .../AvaloniaXamlIlWellKnownTypes.cs | 2 + .../XamlIlBindingPathHelper.cs | 58 +++--- .../XamlIlTrampolineBuilder.cs | 117 ++++++++++++ .../CompiledBindings/CommandAccessorPlugin.cs | 169 +++++++----------- .../CompiledBindings/CompiledBindingPath.cs | 59 ++---- .../CompiledBindingExtensionTests.cs | 4 +- 9 files changed, 237 insertions(+), 186 deletions(-) create mode 100644 src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlTrampolineBuilder.cs diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs index d2f7102b48..dcf9a51de0 100644 --- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs +++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs @@ -124,6 +124,8 @@ namespace Avalonia.Build.Tasks var indexerAccessorClosure = new TypeDefinition("CompiledAvaloniaXaml", "!IndexerAccessorFactoryClosure", TypeAttributes.Class, asm.MainModule.TypeSystem.Object); asm.MainModule.Types.Add(indexerAccessorClosure); + var trampolineBuilder = new TypeDefinition("CompiledAvaloniaXaml", "XamlIlTrampolines", + TypeAttributes.Class, asm.MainModule.TypeSystem.Object); var (xamlLanguage , emitConfig) = AvaloniaXamlIlLanguage.Configure(typeSystem); var compilerConfig = new AvaloniaXamlIlCompilerConfiguration(typeSystem, @@ -133,6 +135,7 @@ namespace Avalonia.Build.Tasks AvaloniaXamlIlLanguage.CustomValueConverter, new XamlIlClrPropertyInfoEmitter(typeSystem.CreateTypeBuilder(clrPropertiesDef)), new XamlIlPropertyInfoAccessorFactoryEmitter(typeSystem.CreateTypeBuilder(indexerAccessorClosure)), + new XamlIlTrampolineBuilder(typeSystem.CreateTypeBuilder(trampolineBuilder)), new DeterministicIdGenerator()); @@ -256,7 +259,7 @@ namespace Avalonia.Build.Tasks (closureName, closureBaseType) => populateBuilder.DefineSubType(closureBaseType, closureName, false), (closureName, returnType, parameterTypes) => - populateBuilder.CreateDelegateSubType(closureName, false, returnType, parameterTypes), + populateBuilder.DefineDelegateSubType(closureName, false, returnType, parameterTypes), res.Uri, res ); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs index ece90762cb..1e2a77c34d 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs @@ -177,11 +177,13 @@ namespace Avalonia.Markup.Xaml.XamlIl var tb = _sreBuilder.DefineType("Builder_" + Guid.NewGuid().ToString("N") + "_" + uri); var clrPropertyBuilder = tb.DefineNestedType("ClrProperties_" + Guid.NewGuid().ToString("N")); var indexerClosureType = _sreBuilder.DefineType("IndexerClosure_" + Guid.NewGuid().ToString("N")); + var trampolineBuilder = _sreBuilder.DefineType("Trampolines_" + Guid.NewGuid().ToString("N")); var compiler = new AvaloniaXamlIlCompiler(new AvaloniaXamlIlCompilerConfiguration(_sreTypeSystem, asm, _sreMappings, _sreXmlns, AvaloniaXamlIlLanguage.CustomValueConverter, new XamlIlClrPropertyInfoEmitter(_sreTypeSystem.CreateTypeBuilder(clrPropertyBuilder)), - new XamlIlPropertyInfoAccessorFactoryEmitter(_sreTypeSystem.CreateTypeBuilder(indexerClosureType))), + new XamlIlPropertyInfoAccessorFactoryEmitter(_sreTypeSystem.CreateTypeBuilder(indexerClosureType)), + new XamlIlTrampolineBuilder(_sreTypeSystem.CreateTypeBuilder(trampolineBuilder))), _sreEmitMappings, _sreContextType) { EnableIlVerification = true }; @@ -196,6 +198,7 @@ namespace Avalonia.Markup.Xaml.XamlIl compiler.ParseAndCompile(xaml, uri?.ToString(), null, _sreTypeSystem.CreateTypeBuilder(tb), overrideType); var created = tb.CreateTypeInfo(); clrPropertyBuilder.CreateTypeInfo(); + trampolineBuilder.CreateTypeInfo(); return LoadOrPopulate(created, rootInstance); } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs index f6f47dce0d..9fc6b5d517 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs @@ -7,6 +7,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions { public XamlIlClrPropertyInfoEmitter ClrPropertyEmitter { get; } public XamlIlPropertyInfoAccessorFactoryEmitter AccessorFactoryEmitter { get; } + public XamlIlTrampolineBuilder TrampolineBuilder { get; } public AvaloniaXamlIlCompilerConfiguration(IXamlTypeSystem typeSystem, IXamlAssembly defaultAssembly, @@ -15,13 +16,16 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions XamlValueConverter customValueConverter, XamlIlClrPropertyInfoEmitter clrPropertyEmitter, XamlIlPropertyInfoAccessorFactoryEmitter accessorFactoryEmitter, + XamlIlTrampolineBuilder trampolineBuilder, IXamlIdentifierGenerator identifierGenerator = null) : base(typeSystem, defaultAssembly, typeMappings, xmlnsMappings, customValueConverter, identifierGenerator) { ClrPropertyEmitter = clrPropertyEmitter; AccessorFactoryEmitter = accessorFactoryEmitter; + TrampolineBuilder = trampolineBuilder; AddExtra(ClrPropertyEmitter); AddExtra(AccessorFactoryEmitter); + AddExtra(TrampolineBuilder); } } } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index c178bb618e..a228ac8acd 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -86,6 +86,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlType IBrush { get; } public IXamlType ImmutableSolidColorBrush { get; } public IXamlConstructor ImmutableSolidColorBrushConstructorColor { get; } + public IXamlType TypeUtilities { get; } public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg) { @@ -189,6 +190,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers IBrush = cfg.TypeSystem.GetType("Avalonia.Media.IBrush"); ImmutableSolidColorBrush = cfg.TypeSystem.GetType("Avalonia.Media.Immutable.ImmutableSolidColorBrush"); ImmutableSolidColorBrushConstructorColor = ImmutableSolidColorBrush.GetConstructor(new List { UInt }); + TypeUtilities = cfg.TypeSystem.GetType("Avalonia.Utilities.TypeUtilities"); } } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs index 8046325ef5..db4cee32b0 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs @@ -14,7 +14,7 @@ using XamlX.Emit; using XamlX.IL; using Avalonia.Utilities; -using XamlIlEmitContext = XamlX.Emit.XamlEmitContext; +using XamlIlEmitContext = XamlX.Emit.XamlEmitContextWithLocals; namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions { @@ -652,58 +652,56 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen) { + var trampolineBuilder = context.Configuration.GetExtra(); + var objectType = context.Configuration.WellKnownTypes.Object; codeGen - .Ldtoken(_executeMethod); - - if (_canExecuteMethod is not null) + .Ldstr(_executeMethod.Name) + .Ldnull() + .Ldftn(trampolineBuilder.EmitCommandExecuteTrampoline(context, _executeMethod)) + .Newobj(context.Configuration.TypeSystem.GetType("System.Action`2") + .MakeGenericType(objectType, objectType) + .GetConstructor(new() { objectType, context.Configuration.TypeSystem.GetType("System.IntPtr") })); + + if (_canExecuteMethod is null) { - codeGen.Ldtoken(_canExecuteMethod); + codeGen.Ldnull(); } else { - using var canExecuteMethodHandle = codeGen.LocalsPool.GetLocal(context.Configuration.TypeSystem.GetType("System.RuntimeMethodHandle")); codeGen - .Ldloca(canExecuteMethodHandle.Local) - .Emit(OpCodes.Initobj) - .Ldloc(canExecuteMethodHandle.Local); + .Ldnull() + .Ldftn(trampolineBuilder.EmitCommandCanExecuteTrampoline(context, _canExecuteMethod)) + .Newobj(context.Configuration.TypeSystem.GetType("System.Func`3") + .MakeGenericType(objectType, objectType, context.Configuration.WellKnownTypes.Boolean) + .GetConstructor(new() { objectType, context.Configuration.TypeSystem.GetType("System.IntPtr") })); } - if (_dependsOnProperties is { Count:> 0 }) + if (_dependsOnProperties is { Count:> 1 }) { - using var dependsOnProperties = codeGen.LocalsPool.GetLocal(context.Configuration.WellKnownTypes.String.MakeArrayType(1)); + using var dependsOnPropertiesArray = context.GetLocalOfType(context.Configuration.WellKnownTypes.String.MakeArrayType(1)); codeGen .Ldc_I4(_dependsOnProperties.Count) .Newarr(context.Configuration.WellKnownTypes.String) - .Stloc(dependsOnProperties.Local); + .Stloc(dependsOnPropertiesArray.Local); - for (var i = 0; i < _dependsOnProperties.Count; i++) + for (int i = 0; i < _dependsOnProperties.Count; i++) { - var prop = _dependsOnProperties[i]; codeGen - .Ldloc(dependsOnProperties.Local) + .Ldloc(dependsOnPropertiesArray.Local) .Ldc_I4(i) - .Ldstr(prop) + .Ldstr(_dependsOnProperties[i]) .Stelem_ref(); } - codeGen.Ldloc(dependsOnProperties.Local); + codeGen.Ldloc(dependsOnPropertiesArray.Local); } else { codeGen.Ldnull(); } - if (_executeMethod.Parameters.Count != 0) - { - codeGen.EmitCall( - context.GetAvaloniaTypes() - .CompiledBindingPathBuilder.FindMethod(m => m.Name == "CommandWithParameter")); - } - else - { - codeGen.EmitCall( - context.GetAvaloniaTypes() - .CompiledBindingPathBuilder.FindMethod(m => m.Name == "Command")); - } + codeGen + .EmitCall(context.GetAvaloniaTypes() + .CompiledBindingPathBuilder.FindMethod(m => m.Name == "Command")); } } @@ -829,7 +827,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions } } - class XamlIlBindingPathNode : XamlAstNode, IXamlIlBindingPathNode, IXamlAstEmitableNode + class XamlIlBindingPathNode : XamlAstNode, IXamlIlBindingPathNode, IXamlAstLocalsEmitableNode { private readonly List _transformElements; diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlTrampolineBuilder.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlTrampolineBuilder.cs new file mode 100644 index 0000000000..283bd99389 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlTrampolineBuilder.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using XamlX.Emit; +using XamlX.IL; +using XamlX.TypeSystem; +using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; +using System.Reflection.Emit; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions +{ + internal class XamlIlTrampolineBuilder + { + private IXamlTypeBuilder _builder; + private Dictionary _trampolines = new(); + + public XamlIlTrampolineBuilder(IXamlTypeBuilder builder) + { + _builder = builder; + } + + public IXamlMethod EmitCommandExecuteTrampoline(XamlEmitContext context, IXamlMethod executeMethod) + { + Debug.Assert(!executeMethod.IsStatic); + string methodName = $"{executeMethod.DeclaringType.GetFqn()}+{executeMethod.Name}_{executeMethod.Parameters.Count}!CommandExecuteTrampoline"; + if (_trampolines.TryGetValue(methodName, out var method)) + { + return method; + } + var trampoline = _builder.DefineMethod( + context.Configuration.WellKnownTypes.Void, + new[] { context.Configuration.WellKnownTypes.Object, context.Configuration.WellKnownTypes.Object }, + methodName, + true, + true, + false); + var gen = trampoline.Generator; + if (executeMethod.DeclaringType.IsValueType) + { + gen.Ldarg_0() + .Unbox(executeMethod.DeclaringType); + } + else + { + gen.Ldarg_0() + .Castclass(executeMethod.DeclaringType); + } + if (executeMethod.Parameters.Count != 0) + { + Debug.Assert(executeMethod.Parameters.Count == 1); + if (executeMethod.Parameters[0] != context.Configuration.WellKnownTypes.Object) + { + var convertedValue = gen.DefineLocal(context.Configuration.WellKnownTypes.Object); + gen.Ldtype(executeMethod.Parameters[0]) + .Ldarg(1) + .EmitCall(context.Configuration.WellKnownTypes.CultureInfo.FindMethod(m => m.Name == "get_CurrentCulture")) + .Ldloca(convertedValue) + .EmitCall( + context.GetAvaloniaTypes().TypeUtilities.FindMethod(m => m.Name == "TryConvert"), + swallowResult: true) + .Ldloc(convertedValue) + .Unbox_Any(executeMethod.Parameters[0]); + } + else + { + gen.Ldarg(1); + } + } + gen.Emit(OpCodes.Tailcall); + gen.EmitCall(executeMethod, swallowResult: true); + gen.Ret(); + + _trampolines.Add(methodName, trampoline); + return trampoline; + } + + public IXamlMethod EmitCommandCanExecuteTrampoline(XamlEmitContext context, IXamlMethod canExecuteMethod) + { + Debug.Assert(!canExecuteMethod.IsStatic); + Debug.Assert(canExecuteMethod.Parameters.Count == 1); + Debug.Assert(canExecuteMethod.ReturnType == context.Configuration.WellKnownTypes.Boolean); + string methodName = $"{canExecuteMethod.DeclaringType.GetFqn()}+{canExecuteMethod.Name}!CommandCanExecuteTrampoline"; + if (_trampolines.TryGetValue(methodName, out var method)) + { + return method; + } + var trampoline = _builder.DefineMethod( + context.Configuration.WellKnownTypes.Boolean, + new[] { context.Configuration.WellKnownTypes.Object, context.Configuration.WellKnownTypes.Object }, + methodName, + true, + true, + false); + if (canExecuteMethod.DeclaringType.IsValueType) + { + trampoline.Generator + .Ldarg_0() + .Unbox(canExecuteMethod.DeclaringType); + } + else + { + trampoline.Generator + .Ldarg_0() + .Castclass(canExecuteMethod.DeclaringType); + } + trampoline.Generator + .Ldarg(1) + .Emit(OpCodes.Tailcall) + .EmitCall(canExecuteMethod) + .Ret(); + + _trampolines.Add(methodName, trampoline); + return trampoline; + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs index beb76c8100..970cc767f7 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs @@ -12,11 +12,15 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings { internal class CommandAccessorPlugin : IPropertyAccessorPlugin { - private readonly Func, IPropertyAccessor> _commandAccessorFactory; + private readonly Action _execute; + private readonly Func _canExecute; + private readonly ISet _dependsOnProperties; - public CommandAccessorPlugin(Func, IPropertyAccessor> commandAccessorFactory) + public CommandAccessorPlugin(Action execute, Func canExecute, ISet dependsOnProperties) { - _commandAccessorFactory = commandAccessorFactory; + _execute = execute; + _canExecute = canExecute; + _dependsOnProperties = dependsOnProperties; } public bool Match(object obj, string propertyName) @@ -26,25 +30,76 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings public IPropertyAccessor Start(WeakReference reference, string propertyName) { - return _commandAccessorFactory(reference); + return new CommandAccessor(reference, _execute, _canExecute, _dependsOnProperties); } - internal abstract class CommandAccessorBase : PropertyAccessorBase + private sealed class CommandAccessor : PropertyAccessorBase { private readonly WeakReference _reference; + private Command _command; private readonly ISet _dependsOnProperties; - public CommandAccessorBase(WeakReference reference, ISet dependsOnProperties) + public CommandAccessor(WeakReference reference, Action execute, Func canExecute, ISet dependsOnProperties) { Contract.Requires(reference != null); _reference = reference; _dependsOnProperties = dependsOnProperties; + _command = new Command(reference, execute, canExecute); + } - public override Type PropertyType => typeof(ICommand); + public override object Value => _reference.TryGetTarget(out var _) ? _command : null; + + private void RaiseCanExecuteChanged() + { + _command.RaiseCanExecuteChanged(); + } + + private sealed class Command : ICommand + { + private readonly WeakReference _target; + private readonly Action _execute; + private readonly Func _canExecute; + + public event EventHandler CanExecuteChanged; + + public Command(WeakReference target, Action execute, Func canExecute) + { + _target = target; + _execute = execute; + _canExecute = canExecute; + } + + public void RaiseCanExecuteChanged() + { + Threading.Dispatcher.UIThread.Post(() => CanExecuteChanged?.Invoke(this, EventArgs.Empty) + , Threading.DispatcherPriority.Input); + } + + public bool CanExecute(object parameter) + { + if (_target.TryGetTarget(out var target)) + { + if (_canExecute == null) + { + return true; + } + return _canExecute(target, parameter); + } + return false; + } - protected abstract void RaiseCanExecuteChanged(); + public void Execute(object parameter) + { + if (_target.TryGetTarget(out var target)) + { + _execute(target, parameter); + } + } + } + + public override Type PropertyType => typeof(ICommand); public override bool SetValue(object value, BindingPriority priority) { @@ -55,7 +110,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings { if (string.IsNullOrEmpty(e.PropertyName) || _dependsOnProperties.Contains(e.PropertyName)) { - SendCurrentValue(); + RaiseCanExecuteChanged(); } } @@ -97,101 +152,5 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings } } } - - internal sealed class CommandWithParameterAccessor : CommandAccessorBase - { - private Command _command; - - public CommandWithParameterAccessor(WeakReference target, ISet dependsOnProperties, Action execute, Func canExecute) - : base(target, dependsOnProperties) - { - _command = new Command(execute, canExecute); - } - - public override object Value => _command; - - protected override void RaiseCanExecuteChanged() - { - _command.RaiseCanExecuteChanged(); - } - - private sealed class Command : ICommand - { - private readonly Action _execute; - private readonly Func _canExecute; - - public event EventHandler CanExecuteChanged; - - public Command(Action execute, Func canExecute) - { - _execute = execute; - _canExecute = canExecute; - } - - public void RaiseCanExecuteChanged() - { - Threading.Dispatcher.UIThread.Post(() => CanExecuteChanged?.Invoke(this, EventArgs.Empty) - , Threading.DispatcherPriority.Input); - } - - public bool CanExecute(object parameter) - { - return _canExecute(parameter); - } - - public void Execute(object parameter) - { - _execute((T)parameter); - } - } - } - - internal sealed class CommandWithoutParameterAccessor : CommandAccessorBase - { - private Command _command; - - public CommandWithoutParameterAccessor(WeakReference target, ISet dependsOnProperties, Action execute, Func canExecute) - : base(target, dependsOnProperties) - { - _command = new Command(execute, canExecute); - } - - public override object Value => _command; - - protected override void RaiseCanExecuteChanged() - { - _command.RaiseCanExecuteChanged(); - } - - private sealed class Command : ICommand - { - private readonly Action _execute; - private readonly Func _canExecute; - - public event EventHandler CanExecuteChanged; - - public Command(Action execute, Func canExecute) - { - _execute = execute; - _canExecute = canExecute; - } - - public void RaiseCanExecuteChanged() - { - Threading.Dispatcher.UIThread.Post(() => CanExecuteChanged?.Invoke(this, EventArgs.Empty) - , Threading.DispatcherPriority.Input); - } - - public bool CanExecute(object parameter) - { - return _canExecute(parameter); - } - - public void Execute(object parameter) - { - _execute(); - } - } - } } } diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs index b5ab24a74e..73a14fd437 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs @@ -37,8 +37,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings case PropertyElement prop: node = new PropertyAccessorNode(prop.Property.Name, enableValidation, new PropertyInfoAccessorPlugin(prop.Property, prop.AccessorFactory)); break; - case IMethodAsCommandElement methodAsCommand: - node = new PropertyAccessorNode(methodAsCommand.ExecuteMethod.Name, enableValidation, new CommandAccessorPlugin(methodAsCommand.CreateAccessor)); + case MethodAsCommandElement methodAsCommand: + node = new PropertyAccessorNode(methodAsCommand.MethodName, enableValidation, new CommandAccessorPlugin(methodAsCommand.ExecuteMethod, methodAsCommand.CanExecuteMethod, methodAsCommand.DependsOnProperties)); break; case MethodAsDelegateElement methodAsDelegate: node = new PropertyAccessorNode(methodAsDelegate.Method.Name, enableValidation, new MethodAccessorPlugin(methodAsDelegate.Method, methodAsDelegate.DelegateType)); @@ -105,15 +105,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings return this; } - public CompiledBindingPathBuilder Command(RuntimeMethodHandle executeMethod, RuntimeMethodHandle canExecuteMethod, string[] dependsOnProperties) + public CompiledBindingPathBuilder Command(string methodName, Action executeHelper, Func canExecuteHelper, string[] dependsOnProperties) { - _elements.Add(new MethodAsCommandElement(executeMethod, canExecuteMethod, dependsOnProperties)); - return this; - } - - public CompiledBindingPathBuilder CommandWithParameter(RuntimeMethodHandle executeMethod, RuntimeMethodHandle canExecuteMethod, string[] dependsOnProperties) - { - _elements.Add(new MethodAsCommandElement(executeMethod, canExecuteMethod, dependsOnProperties)); + _elements.Add(new MethodAsCommandElement(methodName, executeHelper, canExecuteHelper, dependsOnProperties ?? Array.Empty())); return this; } @@ -216,49 +210,20 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings public Type DelegateType { get; } } - internal interface IMethodAsCommandElement - { - MethodInfo ExecuteMethod { get; } - IPropertyAccessor CreateAccessor(WeakReference obj); - } - - internal class MethodAsCommandElement : ICompiledBindingPathElement, IMethodAsCommandElement - { - public MethodAsCommandElement(RuntimeMethodHandle executeMethod, RuntimeMethodHandle canExecuteMethod, string[] dependsOnElements) - { - ExecuteMethod = (MethodInfo)MethodBase.GetMethodFromHandle(executeMethod); - CanExecuteMethod = canExecuteMethod != default ? (MethodInfo)MethodBase.GetMethodFromHandle(canExecuteMethod) : null; - DependsOnProperties = new HashSet(dependsOnElements); - } - - public MethodInfo ExecuteMethod { get; } - public MethodInfo CanExecuteMethod { get; } - public HashSet DependsOnProperties { get; } - - public IPropertyAccessor CreateAccessor(WeakReference obj) - { - obj.TryGetTarget(out object target); - return new CommandAccessorPlugin.CommandWithoutParameterAccessor(obj, DependsOnProperties, (Action)ExecuteMethod.CreateDelegate(typeof(Action), target), (Func)CanExecuteMethod?.CreateDelegate(typeof(Func), target)); - } - } - internal class MethodAsCommandElement : ICompiledBindingPathElement, IMethodAsCommandElement + internal class MethodAsCommandElement : ICompiledBindingPathElement { - public MethodAsCommandElement(RuntimeMethodHandle executeMethod, RuntimeMethodHandle canExecuteMethod, string[] dependsOnElements) + public MethodAsCommandElement(string methodName, Action executeHelper, Func canExecuteHelper, string[] dependsOnElements) { - ExecuteMethod = (MethodInfo)MethodBase.GetMethodFromHandle(executeMethod); - CanExecuteMethod = canExecuteMethod != default ? (MethodInfo)MethodBase.GetMethodFromHandle(canExecuteMethod) : null; + MethodName = methodName; + ExecuteMethod = executeHelper; + CanExecuteMethod = canExecuteHelper; DependsOnProperties = new HashSet(dependsOnElements); } - public MethodInfo ExecuteMethod { get; } - public MethodInfo CanExecuteMethod { get; } + public string MethodName { get; } + public Action ExecuteMethod { get; } + public Func CanExecuteMethod { get; } public HashSet DependsOnProperties { get; } - - public IPropertyAccessor CreateAccessor(WeakReference obj) - { - obj.TryGetTarget(out object target); - return new CommandAccessorPlugin.CommandWithParameterAccessor(obj, DependsOnProperties, (Action)ExecuteMethod.CreateDelegate(typeof(Action), target), (Func)CanExecuteMethod?.CreateDelegate(typeof(Func), target)); - } } internal interface IStronglyTypedStreamElement : ICompiledBindingPathElement diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs index 18c2ad663c..ecfb54a624 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs @@ -1181,7 +1181,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests' x:DataType='local:MethodAsCommandDataContext'> -