浏览代码

Added reflection-free API for weak events

Nikita Tsukanov 3 年之前
父节点
当前提交
e6c08a13d7

+ 6 - 15
src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs

@@ -59,7 +59,7 @@ namespace Avalonia.Collections
         }
         }
 
 
         private class WeakCollectionChangedObservable : LightweightObservableBase<NotifyCollectionChangedEventArgs>,
         private class WeakCollectionChangedObservable : LightweightObservableBase<NotifyCollectionChangedEventArgs>,
-            IWeakSubscriber<NotifyCollectionChangedEventArgs>
+            IWeakEventSubscriber<NotifyCollectionChangedEventArgs>
         {
         {
             private WeakReference<INotifyCollectionChanged> _sourceReference;
             private WeakReference<INotifyCollectionChanged> _sourceReference;
 
 
@@ -68,31 +68,22 @@ namespace Avalonia.Collections
                 _sourceReference = source;
                 _sourceReference = source;
             }
             }
 
 
-            public void OnEvent(object? sender, NotifyCollectionChangedEventArgs e)
+            public void OnEvent(object? sender,
+                WeakEvent ev,
+                NotifyCollectionChangedEventArgs e)
             {
             {
                 PublishNext(e);
                 PublishNext(e);
             }
             }
-
             protected override void Initialize()
             protected override void Initialize()
             {
             {
                 if (_sourceReference.TryGetTarget(out var instance))
                 if (_sourceReference.TryGetTarget(out var instance))
-                {
-                    WeakSubscriptionManager.Subscribe(
-                    instance,
-                    nameof(instance.CollectionChanged),
-                    this);
-                }
+                    WeakEvents.CollectionChanged.Subscribe(instance, this);
             }
             }
 
 
             protected override void Deinitialize()
             protected override void Deinitialize()
             {
             {
                 if (_sourceReference.TryGetTarget(out var instance))
                 if (_sourceReference.TryGetTarget(out var instance))
-                {
-                    WeakSubscriptionManager.Unsubscribe(
-                        instance,
-                        nameof(instance.CollectionChanged),
-                        this);
-                }
+                    WeakEvents.CollectionChanged.Unsubscribe(instance, this);
             }
             }
         }
         }
     }
     }

+ 4 - 6
src/Avalonia.Base/Data/Core/IndexerNodeBase.cs

@@ -23,18 +23,16 @@ namespace Avalonia.Data.Core
 
 
             if (incc != null)
             if (incc != null)
             {
             {
-                inputs.Add(WeakObservable.FromEventPattern<INotifyCollectionChanged, NotifyCollectionChangedEventArgs>(
-                    incc,
-                    nameof(incc.CollectionChanged))
+                inputs.Add(WeakObservable.FromEventPattern(
+                    incc, WeakEvents.CollectionChanged)
                     .Where(x => ShouldUpdate(x.Sender, x.EventArgs))
                     .Where(x => ShouldUpdate(x.Sender, x.EventArgs))
                     .Select(_ => GetValue(target)));
                     .Select(_ => GetValue(target)));
             }
             }
 
 
             if (inpc != null)
             if (inpc != null)
             {
             {
-                inputs.Add(WeakObservable.FromEventPattern<INotifyPropertyChanged, PropertyChangedEventArgs>(
-                    inpc,
-                    nameof(inpc.PropertyChanged))
+                inputs.Add(WeakObservable.FromEventPattern(
+                    inpc, WeakEvents.PropertyChanged)
                     .Where(x => ShouldUpdate(x.Sender, x.EventArgs))
                     .Where(x => ShouldUpdate(x.Sender, x.EventArgs))
                     .Select(_ => GetValue(target)));
                     .Select(_ => GetValue(target)));
             }
             }

+ 10 - 10
src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs

@@ -11,6 +11,12 @@ namespace Avalonia.Data.Core.Plugins
     /// </summary>
     /// </summary>
     public class IndeiValidationPlugin : IDataValidationPlugin
     public class IndeiValidationPlugin : IDataValidationPlugin
     {
     {
+        private static readonly WeakEvent<INotifyDataErrorInfo, DataErrorsChangedEventArgs>
+            ErrorsChangedWeakEvent = WeakEvent.Register<INotifyDataErrorInfo, DataErrorsChangedEventArgs>(
+                (s, h) => s.ErrorsChanged += h,
+                (s, h) => s.ErrorsChanged -= h
+            );
+
         /// <inheritdoc/>
         /// <inheritdoc/>
         public bool Match(WeakReference<object?> reference, string memberName)
         public bool Match(WeakReference<object?> reference, string memberName)
         {
         {
@@ -25,7 +31,7 @@ namespace Avalonia.Data.Core.Plugins
             return new Validator(reference, name, accessor);
             return new Validator(reference, name, accessor);
         }
         }
 
 
-        private class Validator : DataValidationBase, IWeakSubscriber<DataErrorsChangedEventArgs>
+        private class Validator : DataValidationBase, IWeakEventSubscriber<DataErrorsChangedEventArgs>
         {
         {
             private readonly WeakReference<object?> _reference;
             private readonly WeakReference<object?> _reference;
             private readonly string _name;
             private readonly string _name;
@@ -37,7 +43,7 @@ namespace Avalonia.Data.Core.Plugins
                 _name = name;
                 _name = name;
             }
             }
 
 
-            void IWeakSubscriber<DataErrorsChangedEventArgs>.OnEvent(object? sender, DataErrorsChangedEventArgs e)
+            void IWeakEventSubscriber<DataErrorsChangedEventArgs>.OnEvent(object? notifyDataErrorInfo, WeakEvent ev, DataErrorsChangedEventArgs e)
             {
             {
                 if (e.PropertyName == _name || string.IsNullOrEmpty(e.PropertyName))
                 if (e.PropertyName == _name || string.IsNullOrEmpty(e.PropertyName))
                 {
                 {
@@ -51,10 +57,7 @@ namespace Avalonia.Data.Core.Plugins
 
 
                 if (target != null)
                 if (target != null)
                 {
                 {
-                    WeakSubscriptionManager.Subscribe(
-                        target,
-                        nameof(target.ErrorsChanged),
-                        this);
+                    ErrorsChangedWeakEvent.Subscribe(target, this);
                 }
                 }
 
 
                 base.SubscribeCore();
                 base.SubscribeCore();
@@ -66,10 +69,7 @@ namespace Avalonia.Data.Core.Plugins
 
 
                 if (target != null)
                 if (target != null)
                 {
                 {
-                    WeakSubscriptionManager.Unsubscribe(
-                        target,
-                        nameof(target.ErrorsChanged),
-                        this);
+                    ErrorsChangedWeakEvent.Unsubscribe(target, this);
                 }
                 }
 
 
                 base.UnsubscribeCore();
                 base.UnsubscribeCore();

+ 8 - 16
src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs

@@ -1,5 +1,6 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Collections.Specialized;
 using System.ComponentModel;
 using System.ComponentModel;
 using System.Reflection;
 using System.Reflection;
 using Avalonia.Utilities;
 using Avalonia.Utilities;
@@ -85,7 +86,7 @@ namespace Avalonia.Data.Core.Plugins
             return found;
             return found;
         }
         }
 
 
-        private class Accessor : PropertyAccessorBase, IWeakSubscriber<PropertyChangedEventArgs>
+        private class Accessor : PropertyAccessorBase, IWeakEventSubscriber<PropertyChangedEventArgs>
         {
         {
             private readonly WeakReference<object?> _reference;
             private readonly WeakReference<object?> _reference;
             private readonly PropertyInfo _property;
             private readonly PropertyInfo _property;
@@ -129,7 +130,8 @@ namespace Avalonia.Data.Core.Plugins
                 return false;
                 return false;
             }
             }
 
 
-            void IWeakSubscriber<PropertyChangedEventArgs>.OnEvent(object? sender, PropertyChangedEventArgs e)
+            void IWeakEventSubscriber<PropertyChangedEventArgs>.
+                OnEvent(object? notifyPropertyChanged, WeakEvent ev, PropertyChangedEventArgs e)
             {
             {
                 if (e.PropertyName == _property.Name || string.IsNullOrEmpty(e.PropertyName))
                 if (e.PropertyName == _property.Name || string.IsNullOrEmpty(e.PropertyName))
                 {
                 {
@@ -148,13 +150,8 @@ namespace Avalonia.Data.Core.Plugins
             {
             {
                 var inpc = GetReferenceTarget() as INotifyPropertyChanged;
                 var inpc = GetReferenceTarget() as INotifyPropertyChanged;
 
 
-                if (inpc != null)
-                {
-                    WeakSubscriptionManager.Unsubscribe(
-                        inpc,
-                        nameof(inpc.PropertyChanged),
-                        this);
-                }
+                if (inpc != null) 
+                    WeakEvents.PropertyChanged.Unsubscribe(inpc, this);
             }
             }
 
 
             private object? GetReferenceTarget()
             private object? GetReferenceTarget()
@@ -178,13 +175,8 @@ namespace Avalonia.Data.Core.Plugins
             {
             {
                 var inpc = GetReferenceTarget() as INotifyPropertyChanged;
                 var inpc = GetReferenceTarget() as INotifyPropertyChanged;
 
 
-                if (inpc != null)
-                {
-                    WeakSubscriptionManager.Subscribe(
-                        inpc,
-                        nameof(inpc.PropertyChanged),
-                        this);
-                }
+                if (inpc != null) 
+                    WeakEvents.PropertyChanged.Subscribe(inpc, this);
             }
             }
         }
         }
     }
     }

+ 12 - 0
src/Avalonia.Base/Utilities/IWeakEventSubscriber.cs

@@ -0,0 +1,12 @@
+using System;
+
+namespace Avalonia.Utilities;
+
+/// <summary>
+/// Defines a listener to a event subscribed vis the <see cref="WeakEvent{TTarget, TEventArgs}"/>.
+/// </summary>
+/// <typeparam name="TEventArgs">The type of the event arguments.</typeparam>
+public interface IWeakEventSubscriber<in TEventArgs> where TEventArgs : EventArgs
+{
+    void OnEvent(object? sender, WeakEvent ev, TEventArgs e);
+}

+ 175 - 0
src/Avalonia.Base/Utilities/WeakEvent.cs

@@ -0,0 +1,175 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+namespace Avalonia.Utilities;
+
+/// <summary>
+/// Manages subscriptions to events using weak listeners.
+/// </summary>
+public class WeakEvent<TSender, TEventArgs> : WeakEvent where TEventArgs : EventArgs where TSender : class
+{
+    private readonly Func<TSender, EventHandler<TEventArgs>, Action> _subscribe;
+
+    readonly ConditionalWeakTable<object, Subscription> _subscriptions = new();
+
+    internal WeakEvent(
+        Action<TSender, EventHandler<TEventArgs>> subscribe,
+        Action<TSender, EventHandler<TEventArgs>> unsubscribe)
+    {
+        _subscribe = (t, s) =>
+        {
+            subscribe(t, s);
+            return () => unsubscribe(t, s);
+        };
+    }
+    
+    internal WeakEvent(Func<TSender, EventHandler<TEventArgs>, Action> subscribe)
+    {
+        _subscribe = subscribe;
+    }
+    
+    public void Subscribe(TSender target, IWeakEventSubscriber<TEventArgs> subscriber)
+    {
+        if (!_subscriptions.TryGetValue(target, out var subscription))
+            _subscriptions.Add(target, subscription = new Subscription(this, target));
+        subscription.Add(new WeakReference<IWeakEventSubscriber<TEventArgs>>(subscriber));
+    }
+
+    public void Unsubscribe(TSender target, IWeakEventSubscriber<TEventArgs> subscriber)
+    {
+        if (_subscriptions.TryGetValue(target, out var subscription)) 
+            subscription.Remove(subscriber);
+    }
+
+    private class Subscription
+    {
+        private readonly WeakEvent<TSender, TEventArgs> _ev;
+        private readonly TSender _target;
+
+        private WeakReference<IWeakEventSubscriber<TEventArgs>>?[] _data =
+            new WeakReference<IWeakEventSubscriber<TEventArgs>>[16];
+        private int _count;
+        private readonly Action _unsubscribe;
+
+        public Subscription(WeakEvent<TSender, TEventArgs> ev, TSender target)
+        {
+            _ev = ev;
+            _target = target;
+
+            _unsubscribe = ev._subscribe(target, OnEvent);
+        }
+
+        void Destroy()
+        {
+            _unsubscribe();
+            _ev._subscriptions.Remove(_target);
+        }
+
+        public void Add(WeakReference<IWeakEventSubscriber<TEventArgs>> s)
+        {
+            if (_count == _data.Length)
+            {
+                //Extend capacity
+                var extendedData = new WeakReference<IWeakEventSubscriber<TEventArgs>>?[_data.Length * 2];
+                Array.Copy(_data, extendedData, _data.Length);
+                _data = extendedData;
+            }
+
+            _data[_count] = s;
+            _count++;
+        }
+
+        public void Remove(IWeakEventSubscriber<TEventArgs> s)
+        {
+            var removed = false;
+
+            for (int c = 0; c < _count; ++c)
+            {
+                var reference = _data[c];
+
+                if (reference != null && reference.TryGetTarget(out var instance) && instance == s)
+                {
+                    _data[c] = null;
+                    removed = true;
+                }
+            }
+
+            if (removed)
+            {
+                Compact();
+            }
+        }
+
+        void Compact()
+        {
+            int empty = -1;
+            for (var c = 0; c < _count; c++)
+            {
+                var r = _data[c];
+                //Mark current index as first empty
+                if (r == null && empty == -1)
+                    empty = c;
+                //If current element isn't null and we have an empty one
+                if (r != null && empty != -1)
+                {
+                    _data[c] = null;
+                    _data[empty] = r;
+                    empty++;
+                }
+            }
+
+            if (empty != -1)
+                _count = empty;
+            if (_count == 0)
+                Destroy();
+        }
+
+        void OnEvent(object? sender, TEventArgs eventArgs)
+        {
+            var needCompact = false;
+            for (var c = 0; c < _count; c++)
+            {
+                var r = _data[c];
+                if (r?.TryGetTarget(out var sub) == true)
+                    sub!.OnEvent(_target, _ev, eventArgs);
+                else
+                    needCompact = true;
+            }
+
+            if (needCompact)
+                Compact();
+        }
+    }
+
+}
+
+public class WeakEvent
+{
+    public static WeakEvent<TSender, TEventArgs> Register<TSender, TEventArgs>(
+        Action<TSender, EventHandler<TEventArgs>> subscribe,
+        Action<TSender, EventHandler<TEventArgs>> unsubscribe) where TSender : class where TEventArgs : EventArgs
+    {
+        return new WeakEvent<TSender, TEventArgs>(subscribe, unsubscribe);
+    }
+    
+    public static WeakEvent<TSender, TEventArgs> Register<TSender, TEventArgs>(
+        Func<TSender, EventHandler<TEventArgs>, Action> subscribe) where TSender : class where TEventArgs : EventArgs
+    {
+        return new WeakEvent<TSender, TEventArgs>(subscribe);
+    }
+    
+    public static WeakEvent<TSender, EventArgs> Register<TSender>(
+        Action<TSender, EventHandler> subscribe,
+        Action<TSender, EventHandler> unsubscribe) where TSender : class
+    {
+        return Register<TSender, EventArgs>((s, h) =>
+        {
+            EventHandler handler = (_, e) => h(s, e);
+            subscribe(s, handler);
+            return () => unsubscribe(s, handler);
+        });
+    }
+}

+ 40 - 0
src/Avalonia.Base/Utilities/WeakEvents.cs

@@ -0,0 +1,40 @@
+using System;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using System.Windows.Input;
+
+namespace Avalonia.Utilities;
+
+public class WeakEvents
+{
+    /// <summary>
+    /// Represents CollectionChanged event from <see cref="INotifyCollectionChanged"/>
+    /// </summary>
+    public static readonly WeakEvent<INotifyCollectionChanged, NotifyCollectionChangedEventArgs>
+        CollectionChanged = WeakEvent.Register<INotifyCollectionChanged, NotifyCollectionChangedEventArgs>(
+            (c, s) =>
+            {
+                NotifyCollectionChangedEventHandler handler = (_, e) => s(c, e);
+                c.CollectionChanged += handler;
+                return () => c.CollectionChanged -= handler;
+            });
+    
+    /// <summary>
+    /// Represents PropertyChanged event from <see cref="INotifyPropertyChanged"/>
+    /// </summary>
+    public static readonly WeakEvent<INotifyPropertyChanged, PropertyChangedEventArgs>
+        PropertyChanged = WeakEvent.Register<INotifyPropertyChanged, PropertyChangedEventArgs>(
+            (s, h) =>
+            {
+                PropertyChangedEventHandler handler = (_, e) => h(s, e);
+                s.PropertyChanged += handler;
+                return () => s.PropertyChanged -= handler;
+            });
+
+    /// <summary>
+    /// Represents CanExecuteChanged event from <see cref="ICommand"/>
+    /// </summary>
+    public static readonly WeakEvent<ICommand, EventArgs> CommandCanExecuteChanged =
+        WeakEvent.Register<ICommand>((s, h) => s.CanExecuteChanged += h,
+            (s, h) => s.CanExecuteChanged -= h);
+}

+ 34 - 1
src/Avalonia.Base/Utilities/WeakObservable.cs

@@ -18,6 +18,7 @@ namespace Avalonia.Utilities
         /// <param name="target">Object instance that exposes the event to convert.</param>
         /// <param name="target">Object instance that exposes the event to convert.</param>
         /// <param name="eventName">Name of the event to convert.</param>
         /// <param name="eventName">Name of the event to convert.</param>
         /// <returns></returns>
         /// <returns></returns>
+        [Obsolete("Use WeakEvent-based overload")]
         public static IObservable<EventPattern<object, TEventArgs>> FromEventPattern<TTarget, TEventArgs>(
         public static IObservable<EventPattern<object, TEventArgs>> FromEventPattern<TTarget, TEventArgs>(
             TTarget target, 
             TTarget target, 
             string eventName)
             string eventName)
@@ -34,7 +35,9 @@ namespace Avalonia.Utilities
             }).Publish().RefCount();
             }).Publish().RefCount();
         }
         }
 
 
-        private class Handler<TEventArgs> : IWeakSubscriber<TEventArgs> where TEventArgs : EventArgs
+        private class Handler<TEventArgs> 
+            : IWeakSubscriber<TEventArgs>,
+                IWeakEventSubscriber<TEventArgs> where TEventArgs : EventArgs
         {
         {
             private IObserver<EventPattern<object, TEventArgs>> _observer;
             private IObserver<EventPattern<object, TEventArgs>> _observer;
 
 
@@ -47,6 +50,36 @@ namespace Avalonia.Utilities
             {
             {
                 _observer.OnNext(new EventPattern<object, TEventArgs>(sender, e));
                 _observer.OnNext(new EventPattern<object, TEventArgs>(sender, e));
             }
             }
+
+            public void OnEvent(object? sender, WeakEvent ev, TEventArgs e)
+            {
+                _observer.OnNext(new EventPattern<object, TEventArgs>(sender, e));
+            }
         }
         }
+        
+        /// <summary>
+        /// Converts a WeakEvent conforming to the standard .NET event pattern into an observable
+        /// sequence, subscribing weakly.
+        /// </summary>
+        /// <typeparam name="TTarget">The type of target.</typeparam>
+        /// <typeparam name="TEventArgs">The type of the event args.</typeparam>
+        /// <param name="target">Object instance that exposes the event to convert.</param>
+        /// <param name="ev">The weak event to convert.</param>
+        /// <returns></returns>
+        public static IObservable<EventPattern<object, TEventArgs>> FromEventPattern<TTarget, TEventArgs>(
+            TTarget target, WeakEvent<TTarget, TEventArgs> ev)
+            where TEventArgs : EventArgs where TTarget : class
+        {
+            _ = target ?? throw new ArgumentNullException(nameof(target));
+            _ = ev ?? throw new ArgumentNullException(nameof(ev));
+
+            return Observable.Create<EventPattern<object, TEventArgs>>(observer =>
+            {
+                var handler = new Handler<TEventArgs>(observer);
+                ev.Subscribe(target, handler);
+                return () => ev.Unsubscribe(target, handler);
+            }).Publish().RefCount();
+        }
+
     }
     }
 }
 }

+ 1 - 0
src/Avalonia.Base/Utilities/WeakSubscriptionManager.cs

@@ -19,6 +19,7 @@ namespace Avalonia.Utilities
         /// <param name="target">The event source.</param>
         /// <param name="target">The event source.</param>
         /// <param name="eventName">The name of the event.</param>
         /// <param name="eventName">The name of the event.</param>
         /// <param name="subscriber">The subscriber.</param>
         /// <param name="subscriber">The subscriber.</param>
+        [Obsolete("Use WeakEvent")]
         public static void Subscribe<TTarget, TEventArgs>(TTarget target, string eventName, IWeakSubscriber<TEventArgs> subscriber)
         public static void Subscribe<TTarget, TEventArgs>(TTarget target, string eventName, IWeakSubscriber<TEventArgs> subscriber)
             where TEventArgs : EventArgs
             where TEventArgs : EventArgs
         {
         {

+ 4 - 6
src/Avalonia.Controls/NativeMenuItem.cs

@@ -33,7 +33,7 @@ namespace Avalonia.Controls
         }
         }
 
 
 
 
-        class CanExecuteChangedSubscriber : IWeakSubscriber<EventArgs>
+        class CanExecuteChangedSubscriber : IWeakEventSubscriber<EventArgs>
         {
         {
             private readonly NativeMenuItem _parent;
             private readonly NativeMenuItem _parent;
 
 
@@ -42,7 +42,7 @@ namespace Avalonia.Controls
                 _parent = parent;
                 _parent = parent;
             }
             }
 
 
-            public void OnEvent(object sender, EventArgs e)
+            public void OnEvent(object? sender, WeakEvent ev, EventArgs e)
             {
             {
                 _parent.CanExecuteChanged();
                 _parent.CanExecuteChanged();
             }
             }
@@ -160,14 +160,12 @@ namespace Avalonia.Controls
             set
             set
             {
             {
                 if (_command != null)
                 if (_command != null)
-                    WeakSubscriptionManager.Unsubscribe(_command,
-                        nameof(ICommand.CanExecuteChanged), _canExecuteChangedSubscriber);
+                    WeakEvents.CommandCanExecuteChanged.Unsubscribe(_command, _canExecuteChangedSubscriber);
 
 
                 SetAndRaise(CommandProperty, ref _command, value);
                 SetAndRaise(CommandProperty, ref _command, value);
 
 
                 if (_command != null)
                 if (_command != null)
-                    WeakSubscriptionManager.Subscribe(_command,
-                        nameof(ICommand.CanExecuteChanged), _canExecuteChangedSubscriber);
+                    WeakEvents.CommandCanExecuteChanged.Subscribe(_command, _canExecuteChangedSubscriber);
 
 
                 CanExecuteChanged();
                 CanExecuteChanged();
             }
             }

+ 12 - 20
src/Avalonia.Controls/Repeater/ItemsRepeater.cs

@@ -20,7 +20,7 @@ namespace Avalonia.Controls
     /// Represents a data-driven collection control that incorporates a flexible layout system,
     /// Represents a data-driven collection control that incorporates a flexible layout system,
     /// custom views, and virtualization.
     /// custom views, and virtualization.
     /// </summary>
     /// </summary>
-    public class ItemsRepeater : Panel, IChildIndexProvider
+    public class ItemsRepeater : Panel, IChildIndexProvider, IWeakEventSubscriber<EventArgs>
     {
     {
         /// <summary>
         /// <summary>
         /// Defines the <see cref="HorizontalCacheLength"/> property.
         /// Defines the <see cref="HorizontalCacheLength"/> property.
@@ -723,14 +723,8 @@ namespace Avalonia.Controls
             {
             {
                 oldValue.UninitializeForContext(LayoutContext);
                 oldValue.UninitializeForContext(LayoutContext);
 
 
-                WeakEventHandlerManager.Unsubscribe<EventArgs, ItemsRepeater>(
-                    oldValue,
-                    nameof(AttachedLayout.MeasureInvalidated),
-                    InvalidateMeasureForLayout);
-                WeakEventHandlerManager.Unsubscribe<EventArgs, ItemsRepeater>(
-                    oldValue,
-                    nameof(AttachedLayout.ArrangeInvalidated),
-                    InvalidateArrangeForLayout);
+                AttachedLayout.MeasureInvalidatedWeakEvent.Unsubscribe(oldValue, this);
+                AttachedLayout.ArrangeInvalidatedWeakEvent.Unsubscribe(oldValue, this);
 
 
                 // Walk through all the elements and make sure they are cleared
                 // Walk through all the elements and make sure they are cleared
                 foreach (var element in Children)
                 foreach (var element in Children)
@@ -748,14 +742,8 @@ namespace Avalonia.Controls
             {
             {
                 newValue.InitializeForContext(LayoutContext);
                 newValue.InitializeForContext(LayoutContext);
 
 
-                WeakEventHandlerManager.Subscribe<AttachedLayout, EventArgs, ItemsRepeater>(
-                    newValue,
-                    nameof(AttachedLayout.MeasureInvalidated),
-                    InvalidateMeasureForLayout);
-                WeakEventHandlerManager.Subscribe<AttachedLayout, EventArgs, ItemsRepeater>(
-                    newValue,
-                    nameof(AttachedLayout.ArrangeInvalidated),
-                    InvalidateArrangeForLayout);
+                AttachedLayout.MeasureInvalidatedWeakEvent.Subscribe(newValue, this);
+                AttachedLayout.ArrangeInvalidatedWeakEvent.Subscribe(newValue, this);
             }
             }
 
 
             bool isVirtualizingLayout = newValue != null && newValue is VirtualizingLayout;
             bool isVirtualizingLayout = newValue != null && newValue is VirtualizingLayout;
@@ -806,9 +794,13 @@ namespace Avalonia.Controls
             _viewportManager.OnBringIntoViewRequested(e);
             _viewportManager.OnBringIntoViewRequested(e);
         }
         }
 
 
-        private void InvalidateMeasureForLayout(object sender, EventArgs e) => InvalidateMeasure();
-
-        private void InvalidateArrangeForLayout(object sender, EventArgs e) => InvalidateArrange();
+        void IWeakEventSubscriber<EventArgs>.OnEvent(object? sender, WeakEvent ev, EventArgs e)
+        {
+            if(ev == AttachedLayout.ArrangeInvalidatedWeakEvent)
+                InvalidateArrange();
+            else if (ev == AttachedLayout.MeasureInvalidatedWeakEvent)
+                InvalidateMeasure();
+        }
 
 
         private VirtualizingLayoutContext GetLayoutContext()
         private VirtualizingLayoutContext GetLayoutContext()
         {
         {

+ 9 - 6
src/Avalonia.Controls/TopLevel.cs

@@ -34,7 +34,7 @@ namespace Avalonia.Controls
         IStyleHost,
         IStyleHost,
         ILogicalRoot,
         ILogicalRoot,
         ITextInputMethodRoot,
         ITextInputMethodRoot,
-        IWeakSubscriber<ResourcesChangedEventArgs>
+        IWeakEventSubscriber<ResourcesChangedEventArgs>
     {
     {
         /// <summary>
         /// <summary>
         /// Defines the <see cref="ClientSize"/> property.
         /// Defines the <see cref="ClientSize"/> property.
@@ -74,6 +74,12 @@ namespace Avalonia.Controls
         public static readonly StyledProperty<IBrush> TransparencyBackgroundFallbackProperty =
         public static readonly StyledProperty<IBrush> TransparencyBackgroundFallbackProperty =
             AvaloniaProperty.Register<TopLevel, IBrush>(nameof(TransparencyBackgroundFallback), Brushes.White);
             AvaloniaProperty.Register<TopLevel, IBrush>(nameof(TransparencyBackgroundFallback), Brushes.White);
 
 
+        private static readonly WeakEvent<IResourceHost, ResourcesChangedEventArgs>
+            ResourcesChangedWeakEvent = WeakEvent.Register<IResourceHost, ResourcesChangedEventArgs>(
+                (s, h) => s.ResourcesChanged += h,
+                (s, h) => s.ResourcesChanged -= h
+            );
+
         private readonly IInputManager _inputManager;
         private readonly IInputManager _inputManager;
         private readonly IAccessKeyHandler _accessKeyHandler;
         private readonly IAccessKeyHandler _accessKeyHandler;
         private readonly IKeyboardNavigationHandler _keyboardNavigationHandler;
         private readonly IKeyboardNavigationHandler _keyboardNavigationHandler;
@@ -178,10 +184,7 @@ namespace Avalonia.Controls
 
 
             if (((IStyleHost)this).StylingParent is IResourceHost applicationResources)
             if (((IStyleHost)this).StylingParent is IResourceHost applicationResources)
             {
             {
-                WeakSubscriptionManager.Subscribe(
-                    applicationResources,
-                    nameof(IResourceHost.ResourcesChanged),
-                    this);
+                ResourcesChangedWeakEvent.Subscribe(applicationResources, this);
             }
             }
 
 
             impl.LostFocus += PlatformImpl_LostFocus;
             impl.LostFocus += PlatformImpl_LostFocus;
@@ -286,7 +289,7 @@ namespace Avalonia.Controls
         /// <inheritdoc/>
         /// <inheritdoc/>
         IMouseDevice IInputRoot.MouseDevice => PlatformImpl?.MouseDevice;
         IMouseDevice IInputRoot.MouseDevice => PlatformImpl?.MouseDevice;
 
 
-        void IWeakSubscriber<ResourcesChangedEventArgs>.OnEvent(object sender, ResourcesChangedEventArgs e)
+        void IWeakEventSubscriber<ResourcesChangedEventArgs>.OnEvent(object sender, WeakEvent ev, ResourcesChangedEventArgs e)
         {
         {
             ((ILogical)this).NotifyResourcesChanged(e);
             ((ILogical)this).NotifyResourcesChanged(e);
         }
         }

+ 5 - 10
src/Avalonia.Controls/Utils/CollectionChangedEventManager.cs

@@ -83,7 +83,7 @@ namespace Avalonia.Controls.Utils
                 "Collection listener not registered for this collection/listener combination.");
                 "Collection listener not registered for this collection/listener combination.");
         }
         }
 
 
-        private class Entry : IWeakSubscriber<NotifyCollectionChangedEventArgs>, IDisposable
+        private class Entry : IWeakEventSubscriber<NotifyCollectionChangedEventArgs>, IDisposable
         {
         {
             private INotifyCollectionChanged _collection;
             private INotifyCollectionChanged _collection;
 
 
@@ -91,23 +91,18 @@ namespace Avalonia.Controls.Utils
             {
             {
                 _collection = collection;
                 _collection = collection;
                 Listeners = new List<WeakReference<ICollectionChangedListener>>();
                 Listeners = new List<WeakReference<ICollectionChangedListener>>();
-                WeakSubscriptionManager.Subscribe(
-                    _collection,
-                    nameof(INotifyCollectionChanged.CollectionChanged),
-                    this);
+                WeakEvents.CollectionChanged.Subscribe(_collection, this);
             }
             }
 
 
             public List<WeakReference<ICollectionChangedListener>> Listeners { get; }
             public List<WeakReference<ICollectionChangedListener>> Listeners { get; }
 
 
             public void Dispose()
             public void Dispose()
             {
             {
-                WeakSubscriptionManager.Unsubscribe(
-                    _collection,
-                    nameof(INotifyCollectionChanged.CollectionChanged),
-                    this);
+                WeakEvents.CollectionChanged.Unsubscribe(_collection, this);
             }
             }
 
 
-            void IWeakSubscriber<NotifyCollectionChangedEventArgs>.OnEvent(object? sender, NotifyCollectionChangedEventArgs e)
+            void IWeakEventSubscriber<NotifyCollectionChangedEventArgs>.
+                OnEvent(object? notifyCollectionChanged, WeakEvent ev, NotifyCollectionChangedEventArgs e)
             {
             {
                 static void Notify(
                 static void Notify(
                     INotifyCollectionChanged incc,
                     INotifyCollectionChanged incc,

+ 17 - 0
src/Avalonia.Layout/AttachedLayout.cs

@@ -4,6 +4,7 @@
 // Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
 // Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
 
 
 using System;
 using System;
+using Avalonia.Utilities;
 
 
 namespace Avalonia.Layout
 namespace Avalonia.Layout
 {
 {
@@ -19,10 +20,26 @@ namespace Avalonia.Layout
         /// </summary>
         /// </summary>
         public event EventHandler? MeasureInvalidated;
         public event EventHandler? MeasureInvalidated;
 
 
+        /// <summary>
+        /// Occurs when the measurement state (layout) has been invalidated.
+        /// </summary>
+        public static readonly WeakEvent<AttachedLayout, EventArgs> MeasureInvalidatedWeakEvent =
+            WeakEvent.Register<AttachedLayout>(
+                (s, h) => s.MeasureInvalidated += h,
+                (s, h) => s.MeasureInvalidated -= h);
+
         /// <summary>
         /// <summary>
         /// Occurs when the arrange state (layout) has been invalidated.
         /// Occurs when the arrange state (layout) has been invalidated.
         /// </summary>
         /// </summary>
         public event EventHandler? ArrangeInvalidated;
         public event EventHandler? ArrangeInvalidated;
+        
+        /// <summary>
+        /// Occurs when the arrange state (layout) has been invalidated.
+        /// </summary>
+        public static readonly WeakEvent<AttachedLayout, EventArgs> ArrangeInvalidatedWeakEvent =
+            WeakEvent.Register<AttachedLayout>(
+                (s, h) => s.ArrangeInvalidated += h,
+                (s, h) => s.ArrangeInvalidated -= h);
 
 
         /// <summary>
         /// <summary>
         /// Initializes any per-container state the layout requires when it is attached to an
         /// Initializes any per-container state the layout requires when it is attached to an

+ 9 - 27
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs

@@ -72,7 +72,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
         }
         }
     }
     }
 
 
-    internal class InpcPropertyAccessor : PropertyAccessorBase
+    internal class InpcPropertyAccessor : PropertyAccessorBase, IWeakEventSubscriber<PropertyChangedEventArgs>
     {
     {
         protected readonly WeakReference<object> _reference;
         protected readonly WeakReference<object> _reference;
         private readonly IPropertyInfo _property;
         private readonly IPropertyInfo _property;
@@ -110,7 +110,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
             return false;
             return false;
         }
         }
 
 
-        void OnNotifyPropertyChanged(object sender, PropertyChangedEventArgs e)
+        public void OnEvent(object sender, WeakEvent ev, PropertyChangedEventArgs e)
         {
         {
             if (e.PropertyName == _property.Name || string.IsNullOrEmpty(e.PropertyName))
             if (e.PropertyName == _property.Name || string.IsNullOrEmpty(e.PropertyName))
             {
             {
@@ -128,10 +128,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
         {
         {
             if (_reference.TryGetTarget(out var o) && o is INotifyPropertyChanged inpc)
             if (_reference.TryGetTarget(out var o) && o is INotifyPropertyChanged inpc)
             {
             {
-                WeakEventHandlerManager.Unsubscribe<PropertyChangedEventArgs, InpcPropertyAccessor>(
-                    inpc,
-                    nameof(INotifyPropertyChanged.PropertyChanged),
-                    OnNotifyPropertyChanged);
+                WeakEvents.PropertyChanged.Unsubscribe(inpc, this);
             }
             }
         }
         }
 
 
@@ -148,16 +145,11 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
         private void SubscribeToChanges()
         private void SubscribeToChanges()
         {
         {
             if (_reference.TryGetTarget(out var o) && o is INotifyPropertyChanged inpc)
             if (_reference.TryGetTarget(out var o) && o is INotifyPropertyChanged inpc)
-            {
-                WeakEventHandlerManager.Subscribe<INotifyPropertyChanged, PropertyChangedEventArgs, InpcPropertyAccessor>(
-                    inpc,
-                    nameof(INotifyPropertyChanged.PropertyChanged),
-                    OnNotifyPropertyChanged);
-            }
+                WeakEvents.PropertyChanged.Subscribe(inpc, this);
         }
         }
     }
     }
 
 
-    internal class IndexerAccessor : InpcPropertyAccessor
+    internal class IndexerAccessor : InpcPropertyAccessor, IWeakEventSubscriber<NotifyCollectionChangedEventArgs>
     {
     {
         private int _index;
         private int _index;
 
 
@@ -172,27 +164,17 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
         {
         {
             base.SubscribeCore();
             base.SubscribeCore();
             if (_reference.TryGetTarget(out var o) && o is INotifyCollectionChanged incc)
             if (_reference.TryGetTarget(out var o) && o is INotifyCollectionChanged incc)
-            {
-                WeakEventHandlerManager.Subscribe<INotifyCollectionChanged, NotifyCollectionChangedEventArgs, IndexerAccessor>(
-                  incc,
-                  nameof(INotifyCollectionChanged.CollectionChanged),
-                  OnNotifyCollectionChanged);
-            }
+                WeakEvents.CollectionChanged.Subscribe(incc, this);
         }
         }
 
 
         protected override void UnsubscribeCore()
         protected override void UnsubscribeCore()
         {
         {
             base.UnsubscribeCore();
             base.UnsubscribeCore();
             if (_reference.TryGetTarget(out var o) && o is INotifyCollectionChanged incc)
             if (_reference.TryGetTarget(out var o) && o is INotifyCollectionChanged incc)
-            {
-                WeakEventHandlerManager.Unsubscribe<NotifyCollectionChangedEventArgs, IndexerAccessor>(
-                  incc,
-                  nameof(INotifyCollectionChanged.CollectionChanged),
-                  OnNotifyCollectionChanged);
-            }
+                WeakEvents.CollectionChanged.Unsubscribe(incc, this);
         }
         }
-
-        void OnNotifyCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
+        
+        public void OnEvent(object? sender, WeakEvent ev, NotifyCollectionChangedEventArgs args)
         {
         {
             if (ShouldNotifyListeners(args))
             if (ShouldNotifyListeners(args))
             {
             {

+ 75 - 0
tests/Avalonia.Base.UnitTests/WeakEventTests.cs

@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Avalonia.Utilities;
+using Xunit;
+
+namespace Avalonia.Base.UnitTests
+{
+    public class WeakEventManagerTests
+    {
+        class EventSource
+        {
+            public event EventHandler<EventArgs> Event;
+
+            public void Fire()
+            {
+                Event?.Invoke(this, new EventArgs());
+            }
+
+            public static readonly WeakEvent<EventSource, EventArgs> WeakEvent = WeakEvent<EventSource, EventArgs>.Register(
+                (t, s) => t.Event += s,
+                (t, s) => t.Event -= s);
+        }
+
+        class Subscriber : IWeakEventSubscriber<EventArgs>
+        {
+            private readonly Action _onEvent;
+
+            public Subscriber(Action onEvent)
+            {
+                _onEvent = onEvent;
+            }
+
+            public void OnEvent(object sender, WeakEvent ev, EventArgs args)
+            {
+                _onEvent?.Invoke();
+            }
+        }
+
+        [Fact]
+        public void EventShouldBePassedToSubscriber()
+        {
+            bool handled = false;
+            var subscriber = new Subscriber(() => handled = true);
+            var source = new EventSource();
+            EventSource.WeakEvent.Subscribe(source, subscriber);
+
+            source.Fire();
+            Assert.True(handled);
+        }
+
+ 
+        [Fact]
+        public void EventHandlerShouldNotBeKeptAlive()
+        {
+            bool handled = false;
+            var source = new EventSource();
+            AddSubscriber(source, () => handled = true);
+            for (int c = 0; c < 10; c++)
+            {
+                GC.Collect();
+                GC.Collect(3, GCCollectionMode.Forced, true);
+            }
+            source.Fire();
+            Assert.False(handled);
+        }
+
+        private void AddSubscriber(EventSource source, Action func)
+        {
+            EventSource.WeakEvent.Subscribe(source, new Subscriber(func));
+        }
+    }
+}