From bc7c0ece15de566e67a54c49d54cc34990594167 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 23 Mar 2016 18:34:37 +0100 Subject: [PATCH] Ensure active direct bindings don't get collected. --- src/Perspex.Base/PerspexObject.cs | 24 +++++++++- .../PerspexObjectTests_Direct.cs | 44 +++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/src/Perspex.Base/PerspexObject.cs b/src/Perspex.Base/PerspexObject.cs index 5335410222..37ddf671e2 100644 --- a/src/Perspex.Base/PerspexObject.cs +++ b/src/Perspex.Base/PerspexObject.cs @@ -25,6 +25,17 @@ namespace Perspex /// public class PerspexObject : IPerspexObject, IPerspexObjectDebug, INotifyPropertyChanged { + /// + /// Maintains a list of direct property binding subscriptions so that the binding source + /// doesn't get collected. + /// + /// + /// If/when we provide a ClearBindings() method, then this collection will be need to be + /// moved to an instance field and indexed by property, but until that point a static + /// collection will suffice. + /// + private static List s_directBindings = new List(); + /// /// The parent object that inherited values are inherited from. /// @@ -401,9 +412,20 @@ namespace Perspex property, GetDescription(source)); - return source + IDisposable subscription = null; + + subscription = source .Select(x => TypeUtilities.CastOrDefault(x, property.PropertyType)) + .Do(_ => { }, () => s_directBindings.Remove(subscription)) .Subscribe(x => SetValue(property, x)); + + s_directBindings.Add(subscription); + + return Disposable.Create(() => + { + subscription.Dispose(); + s_directBindings.Remove(subscription); + }); } else { diff --git a/tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs b/tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs index 6c5b43888f..ebf0484ba8 100644 --- a/tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs +++ b/tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs @@ -333,6 +333,50 @@ namespace Perspex.Base.UnitTests Assert.Equal("second", target.Foo); } + [Fact] + public void Binding_To_Direct_Property_Does_Not_Get_Collected() + { + var target = new Class2(); + + Func setupBinding = () => + { + var source = new Subject(); + var sub = target.Bind((PerspexProperty)Class1.FooProperty, source); + return new WeakReference(source); + }; + + var weakSource = setupBinding(); + + GC.Collect(); + + Assert.True(weakSource.IsAlive); + } + + [Fact] + public void Binding_To_Direct_Property_Gets_Collected_When_Completed() + { + var target = new Class2(); + + Func setupBinding = () => + { + var source = new Subject(); + var sub = target.Bind((PerspexProperty)Class1.FooProperty, source); + return new WeakReference(source); + }; + + var weakSource = setupBinding(); + + Action completeSource = () => + { + ((ISubject)weakSource.Target).OnCompleted(); + }; + + completeSource(); + GC.Collect(); + + Assert.False(weakSource.IsAlive); + } + [Fact] public void Property_Notifies_Initialized() {