|
@@ -1,5 +1,15 @@
|
|
|
-using System;
|
|
|
+#nullable enable
|
|
|
+
|
|
|
+using System;
|
|
|
+using System.Collections.Generic;
|
|
|
+using System.ComponentModel;
|
|
|
using System.Reactive.Subjects;
|
|
|
+using System.Runtime.CompilerServices;
|
|
|
+using Avalonia.Controls;
|
|
|
+using Avalonia.Data;
|
|
|
+using Avalonia.Data.Core;
|
|
|
+using Avalonia.Markup.Xaml.MarkupExtensions;
|
|
|
+using Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings;
|
|
|
using Avalonia.Threading;
|
|
|
using JetBrains.dotMemoryUnit;
|
|
|
using Xunit;
|
|
@@ -30,7 +40,7 @@ namespace Avalonia.LeakTests
|
|
|
|
|
|
var weakSource = setupBinding();
|
|
|
|
|
|
- GC.Collect();
|
|
|
+ CollectGarbage();
|
|
|
|
|
|
Assert.Equal("foo", target.Foo);
|
|
|
Assert.True(weakSource.IsAlive);
|
|
@@ -56,11 +66,135 @@ namespace Avalonia.LeakTests
|
|
|
};
|
|
|
|
|
|
completeSource();
|
|
|
+ CollectGarbage();
|
|
|
+ Assert.False(weakSource.IsAlive);
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void CompiledBinding_To_InpcProperty_With_Alive_Source_Does_Not_Keep_Target_Alive()
|
|
|
+ {
|
|
|
+ var source = new Class2 { Foo = "foo" };
|
|
|
+
|
|
|
+ WeakReference SetupBinding()
|
|
|
+ {
|
|
|
+ var path = new CompiledBindingPathBuilder()
|
|
|
+ .Property(
|
|
|
+ new ClrPropertyInfo(
|
|
|
+ nameof(Class2.Foo),
|
|
|
+ target => ((Class2)target).Foo,
|
|
|
+ (target, value) => ((Class2)target).Foo = (string?)value,
|
|
|
+ typeof(string)),
|
|
|
+ PropertyInfoAccessorFactory.CreateInpcPropertyAccessor)
|
|
|
+ .Build();
|
|
|
+
|
|
|
+ var target = new TextBlock();
|
|
|
+
|
|
|
+ target.Bind(TextBlock.TextProperty, new CompiledBindingExtension
|
|
|
+ {
|
|
|
+ Source = source,
|
|
|
+ Path = path
|
|
|
+ });
|
|
|
+
|
|
|
+ return new WeakReference(target);
|
|
|
+ }
|
|
|
+
|
|
|
+ var weakTarget = SetupBinding();
|
|
|
+
|
|
|
+ CollectGarbage();
|
|
|
+ Assert.False(weakTarget.IsAlive);
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void CompiledBinding_To_AvaloniaProperty_With_Alive_Source_Does_Not_Keep_Target_Alive()
|
|
|
+ {
|
|
|
+ var source = new StyledElement { Name = "foo" };
|
|
|
+
|
|
|
+ WeakReference SetupBinding()
|
|
|
+ {
|
|
|
+ var path = new CompiledBindingPathBuilder()
|
|
|
+ .Property(StyledElement.NameProperty, PropertyInfoAccessorFactory.CreateAvaloniaPropertyAccessor)
|
|
|
+ .Build();
|
|
|
+
|
|
|
+ var target = new TextBlock();
|
|
|
+
|
|
|
+ target.Bind(TextBlock.TextProperty, new CompiledBindingExtension
|
|
|
+ {
|
|
|
+ Source = source,
|
|
|
+ Path = path
|
|
|
+ });
|
|
|
+
|
|
|
+ return new WeakReference(target);
|
|
|
+ }
|
|
|
+
|
|
|
+ var weakTarget = SetupBinding();
|
|
|
+
|
|
|
+ CollectGarbage();
|
|
|
+ Assert.False(weakTarget.IsAlive);
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void CompiledBinding_To_Method_With_Alive_Source_Does_Not_Keep_Target_Alive()
|
|
|
+ {
|
|
|
+ var source = new Class1();
|
|
|
+
|
|
|
+ WeakReference SetupBinding()
|
|
|
+ {
|
|
|
+ var path = new CompiledBindingPathBuilder()
|
|
|
+ .Command(
|
|
|
+ nameof(Class1.DoSomething),
|
|
|
+ (o, _) => ((Class1) o).DoSomething(),
|
|
|
+ (_, _) => true,
|
|
|
+ [])
|
|
|
+ .Build();
|
|
|
+
|
|
|
+ var target = new Button();
|
|
|
+
|
|
|
+ target.Bind(Button.CommandProperty, new CompiledBindingExtension
|
|
|
+ {
|
|
|
+ Source = source,
|
|
|
+ Path = path
|
|
|
+ });
|
|
|
+
|
|
|
+ return new WeakReference(target);
|
|
|
+ }
|
|
|
+
|
|
|
+ var weakTarget = SetupBinding();
|
|
|
+
|
|
|
+ CollectGarbage();
|
|
|
+ Assert.False(weakTarget.IsAlive);
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void Binding_To_AttachedProperty_With_Alive_Source_Does_Not_Keep_Target_Alive()
|
|
|
+ {
|
|
|
+ var source = new StyledElement { Name = "foo" };
|
|
|
+
|
|
|
+ WeakReference SetupBinding()
|
|
|
+ {
|
|
|
+ var target = new TextBlock();
|
|
|
+
|
|
|
+ target.Bind(TextBlock.TextProperty, new Binding
|
|
|
+ {
|
|
|
+ Source = source,
|
|
|
+ Path = "(Grid.Row)",
|
|
|
+ TypeResolver = (_, name) => name == "Grid" ? typeof(Grid) : throw new NotSupportedException()
|
|
|
+ });
|
|
|
+
|
|
|
+ return new WeakReference(target);
|
|
|
+ }
|
|
|
+
|
|
|
+ var weakTarget = SetupBinding();
|
|
|
+
|
|
|
+ CollectGarbage();
|
|
|
+ Assert.False(weakTarget.IsAlive);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void CollectGarbage()
|
|
|
+ {
|
|
|
GC.Collect();
|
|
|
// Forces WeakEvent compact
|
|
|
Dispatcher.UIThread.RunJobs();
|
|
|
GC.Collect();
|
|
|
- Assert.False(weakSource.IsAlive);
|
|
|
}
|
|
|
|
|
|
private class Class1 : AvaloniaObject
|
|
@@ -83,6 +217,33 @@ namespace Avalonia.LeakTests
|
|
|
get { return _foo; }
|
|
|
set { SetAndRaise(FooProperty, ref _foo, value); }
|
|
|
}
|
|
|
+
|
|
|
+ public void DoSomething()
|
|
|
+ {
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private sealed class Class2 : INotifyPropertyChanged
|
|
|
+ {
|
|
|
+ private string? _foo;
|
|
|
+
|
|
|
+ public string? Foo
|
|
|
+ {
|
|
|
+ get => _foo;
|
|
|
+ set
|
|
|
+ {
|
|
|
+ if (_foo != value)
|
|
|
+ {
|
|
|
+ _foo = value;
|
|
|
+ OnPropertyChanged();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public event PropertyChangedEventHandler? PropertyChanged;
|
|
|
+
|
|
|
+ private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
|
|
+ => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
|
}
|
|
|
}
|
|
|
}
|