[C] detach Behaviors and Triggers on VE finalization (#555)
authorStephane Delcroix <stephane@delcroix.org>
Tue, 6 Dec 2016 12:15:32 +0000 (13:15 +0100)
committerRui Marinho <me@ruimarinho.net>
Tue, 6 Dec 2016 12:15:32 +0000 (12:15 +0000)
* [C] detach Behaviors and Triggers on VE finalization

* update docs

Xamarin.Forms.Core.UnitTests/BehaviorTest.cs
Xamarin.Forms.Core/BindableObject.cs
Xamarin.Forms.Core/VisualElement.cs
docs/Xamarin.Forms.Core/Xamarin.Forms/VisualElement.xml

index 78e0cfb..9021738 100644 (file)
@@ -3,8 +3,9 @@ using NUnit.Framework;
 
 namespace Xamarin.Forms.Core.UnitTests
 {
-       internal class MockBehavior<T> : Behavior<T> where T:BindableObject
+       class MockBehavior<T> : Behavior<T> where T:BindableObject
        {
+               public static int AttachCount { get; set; }
                public bool attached;
                public bool detached;
 
@@ -12,11 +13,13 @@ namespace Xamarin.Forms.Core.UnitTests
                {
                        base.OnAttachedTo (bindable);
                        attached = true;
+                       AttachCount++;
                        AssociatedObject = bindable;
                }
 
                protected override void OnDetachingFrom (BindableObject bindable)
                {
+                       AttachCount--;
                        detached = true;
                        base.OnDetachingFrom (bindable);
                        AssociatedObject = null;
@@ -106,5 +109,42 @@ namespace Xamarin.Forms.Core.UnitTests
                        collection.Remove (behavior);
                        Assert.Null (behavior.AssociatedObject);
                }
+
+               [Test]
+               //https://bugzilla.xamarin.com/show_bug.cgi?id=44074
+               public void TestBehaviorsAreDetachedBeforeGarbageCollection()
+               {
+                       WeakReference weakBindable = null;
+
+                       var attachCount = MockBehavior<VisualElement>.AttachCount;
+
+                       int i = 0;
+                       Action create = null;
+                       create = () =>
+                       {
+                               if (i++ < 1024)
+                               {
+                                       create();
+                                       return;
+                               }
+
+                               var bindable = new MockBindable
+                               {
+                                       Behaviors = {
+                                               new MockBehavior<VisualElement> ()
+                                       }
+                               };
+                               weakBindable = new WeakReference(bindable);
+                       };
+
+                       create();
+
+                       GC.Collect();
+                       GC.WaitForPendingFinalizers();
+                       GC.Collect();
+
+                       Assert.False(weakBindable.IsAlive);
+                       Assert.AreEqual(attachCount, MockBehavior<VisualElement>.AttachCount);
+               }
        }
 }
\ No newline at end of file
index 217f4a9..b5e4145 100644 (file)
@@ -163,6 +163,14 @@ namespace Xamarin.Forms
                        return bpcontext != null && bpcontext.Binding != null;
                }
 
+               internal bool GetIsDefault(BindableProperty targetProperty)
+               {
+                       if (targetProperty == null)
+                               throw new ArgumentNullException(nameof(targetProperty));
+
+                       return GetContext(targetProperty) == null;
+               }
+
                internal object[] GetValues(BindableProperty property0, BindableProperty property1)
                {
                        var values = new object[2];
index 053ddb0..cff1eb1 100644 (file)
@@ -781,5 +781,18 @@ namespace Xamarin.Forms
 
                        public bool Result { get; set; }
                }
+
+               ~VisualElement()
+               {
+                       if (!GetIsDefault(BehaviorsProperty)) {
+                               var behaviors = GetValue(BehaviorsProperty) as AttachedCollection<Behavior>;
+                               behaviors.DetachFrom(this);
+                       }
+
+                       if (!GetIsDefault(TriggersProperty)) {
+                               var triggers = GetValue(TriggersProperty) as AttachedCollection<Trigger>;
+                               triggers.DetachFrom(this);
+                       }
+               }
        }
 }
\ No newline at end of file
index 426d8c6..9b28bf0 100644 (file)
         </remarks>
       </Docs>
     </Member>
+    <Member MemberName="Finalize">
+      <MemberSignature Language="C#" Value="~VisualElement ();" />
+      <MemberSignature Language="ILAsm" Value=".method familyhidebysig virtual instance void Finalize() cil managed" />
+      <MemberType>Method</MemberType>
+      <AssemblyInfo>
+        <AssemblyVersion>2.0.0.0</AssemblyVersion>
+      </AssemblyInfo>
+      <ReturnValue>
+        <ReturnType>System.Void</ReturnType>
+      </ReturnValue>
+      <Parameters />
+      <Docs>
+        <summary>To be added.</summary>
+        <remarks>To be added.</remarks>
+      </Docs>
+    </Member>
     <Member MemberName="Focus">
       <MemberSignature Language="C#" Value="public bool Focus ();" />
       <MemberSignature Language="ILAsm" Value=".method public hidebysig instance bool Focus() cil managed" />