Procházet zdrojové kódy

Merge pull request #1410 from sdoroff/weak-collection-change-subscription

Added Weak CollectionChange Methods
Steven Kirk před 7 roky
rodič
revize
b992e9b4f1

+ 21 - 5
src/Avalonia.Base/Collections/AvaloniaListExtensions.cs

@@ -34,14 +34,18 @@ namespace Avalonia.Collections
         /// <param name="reset">
         /// An action called when the collection is reset.
         /// </param>
+        /// <param name="weakSubscription">
+        /// Indicates if a weak subscription should be used to track changes to the collection.
+        /// </param>
         /// <returns>A disposable used to terminate the subscription.</returns>
         public static IDisposable ForEachItem<T>(
             this IAvaloniaReadOnlyList<T> collection,
             Action<T> added,
             Action<T> removed,
-            Action reset)
+            Action reset,
+            bool weakSubscription = false)
         {
-            return collection.ForEachItem((_, i) => added(i), (_, i) => removed(i), reset);
+            return collection.ForEachItem((_, i) => added(i), (_, i) => removed(i), reset, weakSubscription);
         }
 
         /// <summary>
@@ -63,12 +67,16 @@ namespace Avalonia.Collections
         /// An action called when the collection is reset. This will be followed by calls to 
         /// <paramref name="added"/> for each item present in the collection after the reset.
         /// </param>
+        /// <param name="weakSubscription">
+        /// Indicates if a weak subscription should be used to track changes to the collection.
+        /// </param>
         /// <returns>A disposable used to terminate the subscription.</returns>
         public static IDisposable ForEachItem<T>(
             this IAvaloniaReadOnlyList<T> collection,
             Action<int, T> added,
             Action<int, T> removed,
-            Action reset)
+            Action reset,
+            bool weakSubscription = false)
         {
             void Add(int index, IList items)
             {
@@ -118,9 +126,17 @@ namespace Avalonia.Collections
             };
 
             Add(0, (IList)collection);
-            collection.CollectionChanged += handler;
 
-            return Disposable.Create(() => collection.CollectionChanged -= handler);
+            if (weakSubscription)
+            {
+                return collection.WeakSubscribe(handler);
+            }
+            else
+            {
+                collection.CollectionChanged += handler;
+
+                return Disposable.Create(() => collection.CollectionChanged -= handler);
+            }
         }
 
         public static IAvaloniaReadOnlyList<TDerived> CreateDerivedList<TSource, TDerived>(

+ 127 - 0
src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs

@@ -0,0 +1,127 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Reactive;
+using System.Reactive.Disposables;
+using System.Reactive.Linq;
+using System.Reactive.Subjects;
+using Avalonia.Utilities;
+
+namespace Avalonia.Collections
+{
+    public static class NotifyCollectionChangedExtensions
+    {
+        /// <summary>
+        /// Gets a weak observable for the CollectionChanged event.
+        /// </summary>
+        /// <param name="collection">The collection.</param>
+        /// <returns>An observable.</returns>
+        public static IObservable<NotifyCollectionChangedEventArgs> GetWeakCollectionChangedObservable(
+            this INotifyCollectionChanged collection)
+        {
+            Contract.Requires<ArgumentNullException>(collection != null);
+
+            return new WeakCollectionChangedObservable(new WeakReference<INotifyCollectionChanged>(collection));
+        }
+
+        /// <summary>
+        /// Subcribes to the CollectionChanged event using a weak subscription.
+        /// </summary>
+        /// <param name="collection">The collection.</param>
+        /// <param name="handler">
+        /// An action called when the collection event is raised.
+        /// </param>
+        /// <returns>A disposable used to terminate the subscription.</returns>
+        public static IDisposable WeakSubscribe(
+            this INotifyCollectionChanged collection, 
+            NotifyCollectionChangedEventHandler handler)
+        {
+            Contract.Requires<ArgumentNullException>(collection != null);
+            Contract.Requires<ArgumentNullException>(handler != null);
+
+            return
+                collection.GetWeakCollectionChangedObservable()
+                          .Subscribe(e => handler.Invoke(collection, e));
+        }
+
+        /// <summary>
+        /// Subcribes to the CollectionChanged event using a weak subscription.
+        /// </summary>
+        /// <param name="collection">The collection.</param>
+        /// <param name="handler">
+        /// An action called when the collection event is raised.
+        /// </param>
+        /// <returns>A disposable used to terminate the subscription.</returns>
+        public static IDisposable WeakSubscribe(
+            this INotifyCollectionChanged collection,
+            Action<NotifyCollectionChangedEventArgs> handler)
+        {
+            Contract.Requires<ArgumentNullException>(collection != null);
+            Contract.Requires<ArgumentNullException>(handler != null);
+
+            return
+                collection.GetWeakCollectionChangedObservable()
+                          .Subscribe(handler);
+        }
+
+        private class WeakCollectionChangedObservable : ObservableBase<NotifyCollectionChangedEventArgs>,
+            IWeakSubscriber<NotifyCollectionChangedEventArgs>
+        {
+            private WeakReference<INotifyCollectionChanged> _sourceReference;
+            private readonly Subject<NotifyCollectionChangedEventArgs> _changed = new Subject<NotifyCollectionChangedEventArgs>();
+
+            private int _count;
+
+            public WeakCollectionChangedObservable(WeakReference<INotifyCollectionChanged> source)
+            {
+                _sourceReference = source;
+            }
+
+            public void OnEvent(object sender, NotifyCollectionChangedEventArgs e)
+            {
+                _changed.OnNext(e);
+            }
+
+            protected override IDisposable SubscribeCore(IObserver<NotifyCollectionChangedEventArgs> observer)
+            {
+                if (_sourceReference.TryGetTarget(out INotifyCollectionChanged instance))
+                {
+                    if (_count++ == 0)
+                    {
+                        WeakSubscriptionManager.Subscribe(
+                            instance,
+                            nameof(instance.CollectionChanged),
+                            this);
+                    }
+
+                    return Observable.Using(() => Disposable.Create(DecrementCount), _ => _changed)
+                        .Subscribe(observer);
+                }
+                else
+                {
+                    _changed.OnCompleted();
+                    observer.OnCompleted();
+                    return Disposable.Empty;
+                }
+            }
+
+            private void DecrementCount()
+            {
+                if (--_count == 0)
+                {
+                    if (_sourceReference.TryGetTarget(out INotifyCollectionChanged instance))
+                    {
+                        WeakSubscriptionManager.Unsubscribe(
+                            instance,
+                            nameof(instance.CollectionChanged),
+                            this);
+                    }
+                }
+            }
+        }
+    }
+}

+ 5 - 7
src/Avalonia.Controls/ItemsControl.cs

@@ -1,6 +1,7 @@
 // Copyright (c) The Avalonia Project. All rights reserved.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
+using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.Collections.Specialized;
@@ -54,6 +55,7 @@ namespace Avalonia.Controls
 
         private IEnumerable _items = new AvaloniaList<object>();
         private IItemContainerGenerator _itemContainerGenerator;
+        private IDisposable _itemsCollectionChangedSubscription;
 
         /// <summary>
         /// Initializes static members of the <see cref="ItemsControl"/> class.
@@ -326,12 +328,8 @@ namespace Avalonia.Controls
         /// <param name="e">The event args.</param>
         protected virtual void ItemsChanged(AvaloniaPropertyChangedEventArgs e)
         {
-            var incc = e.OldValue as INotifyCollectionChanged;
-
-            if (incc != null)
-            {
-                incc.CollectionChanged -= ItemsCollectionChanged;
-            }
+            _itemsCollectionChangedSubscription?.Dispose();
+            _itemsCollectionChangedSubscription = null;
 
             var oldValue = e.OldValue as IEnumerable;
             var newValue = e.NewValue as IEnumerable;
@@ -428,7 +426,7 @@ namespace Avalonia.Controls
 
             if (incc != null)
             {
-                incc.CollectionChanged += ItemsCollectionChanged;
+                _itemsCollectionChangedSubscription = incc.WeakSubscribe(ItemsCollectionChanged);
             }
         }