diff --git a/src/Avalonia.Interactivity/EventSubscription.cs b/src/Avalonia.Interactivity/EventSubscription.cs index 9f763edcd3..e8fb1bfaf1 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 HandlerInvokeSignature(Delegate baseHandler, object sender, RoutedEventArgs args); + internal class EventSubscription { + public HandlerInvokeSignature InvokeAdapter { 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..f8d388ec89 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.Reactive.Disposables; -using System.Reactive.Linq; using Avalonia.Layout; using Avalonia.VisualTree; @@ -18,15 +16,14 @@ namespace Avalonia.Interactivity { private Dictionary> _eventHandlers; + private static readonly Dictionary s_invokeHandlerCache = 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 Dictionary> EventHandlers => _eventHandlers ?? (_eventHandlers = new Dictionary>()); /// /// Adds a handler for the specified routed event. @@ -45,24 +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); - } - - var sub = new EventSubscription + var subscription = new EventSubscription { Handler = handler, Routes = routes, AlsoIfHandled = handledEventsToo, }; - subscriptions.Add(sub); - - return Disposable.Create(() => subscriptions.Remove(sub)); + return AddEventSubscription(routedEvent, subscription); } /// @@ -80,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_invokeHandlerCache.TryGetValue(eventArgsType, out var invokeAdapter)) + { + void InvokeAdapter(Delegate baseHandler, object sender, RoutedEventArgs args) + { + var typedHandler = (EventHandler)baseHandler; + var typedArgs = (TEventArgs)args; + + typedHandler(sender, typedArgs); + } + + invokeAdapter = InvokeAdapter; + + s_invokeHandlerCache.Add(eventArgsType, invokeAdapter); + } + + var subscription = new EventSubscription + { + InvokeAdapter = invokeAdapter, + Handler = handler, + Routes = routes, + AlsoIfHandled = handledEventsToo, + }; + + return AddEventSubscription(routedEvent, subscription); } /// @@ -196,10 +213,54 @@ namespace Avalonia.Interactivity if (correctRoute && notFinished) { - sub.Handler.DynamicInvoke(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); + } + } } }