From 04c2dfcc5484891761522764a8359e3b73638c9b Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 8 Sep 2019 14:26:35 +0200 Subject: [PATCH 1/4] Compiled expressions for RoutedEvent. --- .../EventSubscription.cs | 4 +++ src/Avalonia.Interactivity/Interactive.cs | 26 ++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Interactivity/EventSubscription.cs b/src/Avalonia.Interactivity/EventSubscription.cs index 9f763edcd3..ff2a28e570 100644 --- a/src/Avalonia.Interactivity/EventSubscription.cs +++ b/src/Avalonia.Interactivity/EventSubscription.cs @@ -5,8 +5,12 @@ using System; namespace Avalonia.Interactivity { + internal delegate void InvokeSignature(Delegate func, object sender, RoutedEventArgs args); + internal class EventSubscription { + public InvokeSignature RaiseHandler { get; set; } + public Delegate Handler { get; set; } public RoutingStrategies Routes { get; set; } diff --git a/src/Avalonia.Interactivity/Interactive.cs b/src/Avalonia.Interactivity/Interactive.cs index 911fc2130e..033686a8c8 100644 --- a/src/Avalonia.Interactivity/Interactive.cs +++ b/src/Avalonia.Interactivity/Interactive.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Reactive.Disposables; using System.Reactive.Linq; using Avalonia.Layout; @@ -28,6 +29,10 @@ namespace Avalonia.Interactivity get { return _eventHandlers ?? (_eventHandlers = new Dictionary>()); } } + + + private static Dictionary s_invokeCache = new Dictionary(); + /// /// Adds a handler for the specified routed event. /// @@ -53,8 +58,27 @@ namespace Avalonia.Interactivity EventHandlers.Add(routedEvent, subscriptions); } + if (!s_invokeCache.TryGetValue(routedEvent.EventArgsType, out InvokeSignature raiseFunc)) + { + ParameterExpression funcParameter = Expression.Parameter(typeof(Delegate), "func"); + ParameterExpression senderParameter = Expression.Parameter(typeof(object), "sender"); + ParameterExpression argsParameter = Expression.Parameter(typeof(RoutedEventArgs), "args"); + + UnaryExpression convertedFunc = Expression.Convert(funcParameter, typeof(EventHandler<>).MakeGenericType(routedEvent.EventArgsType)); + UnaryExpression convertedArgs = Expression.Convert(argsParameter, routedEvent.EventArgsType); + + InvocationExpression invokeDelegate = Expression.Invoke(convertedFunc, senderParameter, convertedArgs); + + raiseFunc = Expression + .Lambda(invokeDelegate, funcParameter, senderParameter, argsParameter) + .Compile(); + + s_invokeCache.Add(routedEvent.EventArgsType, raiseFunc); + } + var sub = new EventSubscription { + RaiseHandler = raiseFunc, Handler = handler, Routes = routes, AlsoIfHandled = handledEventsToo, @@ -196,7 +220,7 @@ namespace Avalonia.Interactivity if (correctRoute && notFinished) { - sub.Handler.DynamicInvoke(this, e); + sub.RaiseHandler(sub.Handler, this, e); } } } From 4a7f370fc520413a4c59709c8c10ab25048722e9 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 8 Sep 2019 20:23:38 +0200 Subject: [PATCH 2/4] Cleanup. --- src/Avalonia.Interactivity/Interactive.cs | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/Avalonia.Interactivity/Interactive.cs b/src/Avalonia.Interactivity/Interactive.cs index 033686a8c8..59dfbcb8ab 100644 --- a/src/Avalonia.Interactivity/Interactive.cs +++ b/src/Avalonia.Interactivity/Interactive.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reactive.Disposables; -using System.Reactive.Linq; using Avalonia.Layout; using Avalonia.VisualTree; @@ -19,19 +18,14 @@ namespace Avalonia.Interactivity { private Dictionary> _eventHandlers; + private static readonly Dictionary s_invokeCache = new Dictionary(); + /// /// Gets the interactive parent of the object for bubbling and tunneling events. /// IInteractive IInteractive.InteractiveParent => ((IVisual)this).VisualParent as IInteractive; - private Dictionary> EventHandlers - { - get { return _eventHandlers ?? (_eventHandlers = new Dictionary>()); } - } - - - - private static Dictionary s_invokeCache = new Dictionary(); + private Dictionary> EventHandlers => _eventHandlers ?? (_eventHandlers = new Dictionary>()); /// /// Adds a handler for the specified routed event. @@ -58,14 +52,16 @@ namespace Avalonia.Interactivity EventHandlers.Add(routedEvent, subscriptions); } - if (!s_invokeCache.TryGetValue(routedEvent.EventArgsType, out InvokeSignature raiseFunc)) + Type eventArgsType = routedEvent.EventArgsType; + + if (!s_invokeCache.TryGetValue(eventArgsType, out InvokeSignature raiseFunc)) { ParameterExpression funcParameter = Expression.Parameter(typeof(Delegate), "func"); ParameterExpression senderParameter = Expression.Parameter(typeof(object), "sender"); ParameterExpression argsParameter = Expression.Parameter(typeof(RoutedEventArgs), "args"); - UnaryExpression convertedFunc = Expression.Convert(funcParameter, typeof(EventHandler<>).MakeGenericType(routedEvent.EventArgsType)); - UnaryExpression convertedArgs = Expression.Convert(argsParameter, routedEvent.EventArgsType); + UnaryExpression convertedFunc = Expression.Convert(funcParameter, typeof(EventHandler<>).MakeGenericType(eventArgsType)); + UnaryExpression convertedArgs = Expression.Convert(argsParameter, eventArgsType); InvocationExpression invokeDelegate = Expression.Invoke(convertedFunc, senderParameter, convertedArgs); @@ -73,7 +69,7 @@ namespace Avalonia.Interactivity .Lambda(invokeDelegate, funcParameter, senderParameter, argsParameter) .Compile(); - s_invokeCache.Add(routedEvent.EventArgsType, raiseFunc); + s_invokeCache.Add(eventArgsType, raiseFunc); } var sub = new EventSubscription From 5cae6bee7b94253df022fe20ecf48bd4f3660780 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 8 Sep 2019 21:55:17 +0200 Subject: [PATCH 3/4] Simplify creation of invoke adapters. --- .../EventSubscription.cs | 2 +- src/Avalonia.Interactivity/Interactive.cs | 115 ++++++++++++------ 2 files changed, 79 insertions(+), 38 deletions(-) diff --git a/src/Avalonia.Interactivity/EventSubscription.cs b/src/Avalonia.Interactivity/EventSubscription.cs index ff2a28e570..b495142998 100644 --- a/src/Avalonia.Interactivity/EventSubscription.cs +++ b/src/Avalonia.Interactivity/EventSubscription.cs @@ -9,7 +9,7 @@ namespace Avalonia.Interactivity internal class EventSubscription { - public InvokeSignature RaiseHandler { get; set; } + public InvokeSignature InvokeAdapter { get; set; } public Delegate Handler { get; set; } diff --git a/src/Avalonia.Interactivity/Interactive.cs b/src/Avalonia.Interactivity/Interactive.cs index 59dfbcb8ab..0f607241ec 100644 --- a/src/Avalonia.Interactivity/Interactive.cs +++ b/src/Avalonia.Interactivity/Interactive.cs @@ -4,8 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; -using System.Reactive.Disposables; using Avalonia.Layout; using Avalonia.VisualTree; @@ -44,45 +42,14 @@ namespace Avalonia.Interactivity Contract.Requires(routedEvent != null); Contract.Requires(handler != null); - List subscriptions; - - if (!EventHandlers.TryGetValue(routedEvent, out subscriptions)) - { - subscriptions = new List(); - EventHandlers.Add(routedEvent, subscriptions); - } - - Type eventArgsType = routedEvent.EventArgsType; - - if (!s_invokeCache.TryGetValue(eventArgsType, out InvokeSignature raiseFunc)) - { - ParameterExpression funcParameter = Expression.Parameter(typeof(Delegate), "func"); - ParameterExpression senderParameter = Expression.Parameter(typeof(object), "sender"); - ParameterExpression argsParameter = Expression.Parameter(typeof(RoutedEventArgs), "args"); - - UnaryExpression convertedFunc = Expression.Convert(funcParameter, typeof(EventHandler<>).MakeGenericType(eventArgsType)); - UnaryExpression convertedArgs = Expression.Convert(argsParameter, eventArgsType); - - InvocationExpression invokeDelegate = Expression.Invoke(convertedFunc, senderParameter, convertedArgs); - - raiseFunc = Expression - .Lambda(invokeDelegate, funcParameter, senderParameter, argsParameter) - .Compile(); - - s_invokeCache.Add(eventArgsType, raiseFunc); - } - - var sub = new EventSubscription + var subscription = new EventSubscription { - RaiseHandler = raiseFunc, Handler = handler, Routes = routes, AlsoIfHandled = handledEventsToo, }; - subscriptions.Add(sub); - - return Disposable.Create(() => subscriptions.Remove(sub)); + return AddEventSubscription(routedEvent, subscription); } /// @@ -100,7 +67,37 @@ namespace Avalonia.Interactivity RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble, bool handledEventsToo = false) where TEventArgs : RoutedEventArgs { - return AddHandler(routedEvent, (Delegate)handler, routes, handledEventsToo); + Contract.Requires(routedEvent != null); + Contract.Requires(handler != null); + + // EventHandler delegate is not covariant, this forces us to create small wrapper + // that will cast our type erased instance and invoke it. + Type eventArgsType = routedEvent.EventArgsType; + + if (!s_invokeCache.TryGetValue(eventArgsType, out var invokeAdapter)) + { + void InvokeAdapter(Delegate func, object sender, RoutedEventArgs args) + { + var typedHandler = (EventHandler)func; + var typedArgs = (TEventArgs)args; + + typedHandler(sender, typedArgs); + } + + invokeAdapter = InvokeAdapter; + + s_invokeCache.Add(eventArgsType, invokeAdapter); + } + + var subscription = new EventSubscription + { + InvokeAdapter = invokeAdapter, + Handler = handler, + Routes = routes, + AlsoIfHandled = handledEventsToo, + }; + + return AddEventSubscription(routedEvent, subscription); } /// @@ -216,10 +213,54 @@ namespace Avalonia.Interactivity if (correctRoute && notFinished) { - sub.RaiseHandler(sub.Handler, this, e); + if (sub.InvokeAdapter != null) + { + sub.InvokeAdapter(sub.Handler, this, e); + } + else + { + sub.Handler.DynamicInvoke(this, e); + } } } } } + + private List GetEventSubscriptions(RoutedEvent routedEvent) + { + if (!EventHandlers.TryGetValue(routedEvent, out var subscriptions)) + { + subscriptions = new List(); + EventHandlers.Add(routedEvent, subscriptions); + } + + return subscriptions; + } + + private IDisposable AddEventSubscription(RoutedEvent routedEvent, EventSubscription subscription) + { + List subscriptions = GetEventSubscriptions(routedEvent); + + subscriptions.Add(subscription); + + return new UnsubscribeDisposable(subscriptions, subscription); + } + + private sealed class UnsubscribeDisposable : IDisposable + { + private readonly List _subscriptions; + private readonly EventSubscription _subscription; + + public UnsubscribeDisposable(List subscriptions, EventSubscription subscription) + { + _subscriptions = subscriptions; + _subscription = subscription; + } + + public void Dispose() + { + _subscriptions.Remove(_subscription); + } + } } } From f5b317decbf7bededba9d4746a09261d6e6c1aa4 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 8 Sep 2019 22:33:39 +0200 Subject: [PATCH 4/4] Naming. --- src/Avalonia.Interactivity/EventSubscription.cs | 4 ++-- src/Avalonia.Interactivity/Interactive.cs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Interactivity/EventSubscription.cs b/src/Avalonia.Interactivity/EventSubscription.cs index b495142998..e8fb1bfaf1 100644 --- a/src/Avalonia.Interactivity/EventSubscription.cs +++ b/src/Avalonia.Interactivity/EventSubscription.cs @@ -5,11 +5,11 @@ using System; namespace Avalonia.Interactivity { - internal delegate void InvokeSignature(Delegate func, object sender, RoutedEventArgs args); + internal delegate void HandlerInvokeSignature(Delegate baseHandler, object sender, RoutedEventArgs args); internal class EventSubscription { - public InvokeSignature InvokeAdapter { get; set; } + public HandlerInvokeSignature InvokeAdapter { get; set; } public Delegate Handler { get; set; } diff --git a/src/Avalonia.Interactivity/Interactive.cs b/src/Avalonia.Interactivity/Interactive.cs index 0f607241ec..f8d388ec89 100644 --- a/src/Avalonia.Interactivity/Interactive.cs +++ b/src/Avalonia.Interactivity/Interactive.cs @@ -16,7 +16,7 @@ namespace Avalonia.Interactivity { private Dictionary> _eventHandlers; - private static readonly Dictionary s_invokeCache = new Dictionary(); + private static readonly Dictionary s_invokeHandlerCache = new Dictionary(); /// /// Gets the interactive parent of the object for bubbling and tunneling events. @@ -74,11 +74,11 @@ namespace Avalonia.Interactivity // that will cast our type erased instance and invoke it. Type eventArgsType = routedEvent.EventArgsType; - if (!s_invokeCache.TryGetValue(eventArgsType, out var invokeAdapter)) + if (!s_invokeHandlerCache.TryGetValue(eventArgsType, out var invokeAdapter)) { - void InvokeAdapter(Delegate func, object sender, RoutedEventArgs args) + void InvokeAdapter(Delegate baseHandler, object sender, RoutedEventArgs args) { - var typedHandler = (EventHandler)func; + var typedHandler = (EventHandler)baseHandler; var typedArgs = (TEventArgs)args; typedHandler(sender, typedArgs); @@ -86,7 +86,7 @@ namespace Avalonia.Interactivity invokeAdapter = InvokeAdapter; - s_invokeCache.Add(eventArgsType, invokeAdapter); + s_invokeHandlerCache.Add(eventArgsType, invokeAdapter); } var subscription = new EventSubscription