diff --git a/Avalonia.Desktop.slnf b/Avalonia.Desktop.slnf
index 40a3802a59..465e579a44 100644
--- a/Avalonia.Desktop.slnf
+++ b/Avalonia.Desktop.slnf
@@ -13,6 +13,8 @@
"samples\\RenderDemo\\RenderDemo.csproj",
"samples\\SampleControls\\ControlSamples.csproj",
"samples\\Sandbox\\Sandbox.csproj",
+ "samples\\UnloadableAssemblyLoadContext\\UnloadableAssemblyLoadContextPlug\\UnloadableAssemblyLoadContextPlug.csproj",
+ "samples\\UnloadableAssemblyLoadContext\\UnloadableAssemblyLoadContext\\UnloadableAssemblyLoadContext.csproj",
"src\\Avalonia.Base\\Avalonia.Base.csproj",
"src\\Avalonia.Build.Tasks\\Avalonia.Build.Tasks.csproj",
"src\\Avalonia.Controls.ColorPicker\\Avalonia.Controls.ColorPicker.csproj",
diff --git a/Avalonia.sln b/Avalonia.sln
index 1ac6b77e4e..25753461a9 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -1,4 +1,3 @@
-
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
@@ -291,6 +290,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BuildTasks", "BuildTasks",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PInvoke", "tests\TestFiles\BuildTasks\PInvoke\PInvoke.csproj", "{0A948D71-99C5-43E9-BACB-B0BA59EA25B4}"
EndProject
+
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UnloadableAssemblyLoadContext", "UnloadableAssemblyLoadContext", "{9CCA131B-DE95-4D44-8788-C3CAE28574CD}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnloadableAssemblyLoadContext", "samples\UnloadableAssemblyLoadContext\UnloadableAssemblyLoadContext\UnloadableAssemblyLoadContext.csproj", "{D7FE3E0F-3FE0-4F87-A2F5-24F1454D84C0}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnloadableAssemblyLoadContextPlug", "samples\UnloadableAssemblyLoadContext\UnloadableAssemblyLoadContextPlug\UnloadableAssemblyLoadContextPlug.csproj", "{DA5F1FF9-4259-4C54-B443-85CFA226EE6A}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.RenderTests.WpfCompare", "tests\Avalonia.RenderTests.WpfCompare\Avalonia.RenderTests.WpfCompare.csproj", "{9AE1B827-21AC-4063-AB22-C8804B7F931E}"
EndProject
Global
@@ -677,6 +683,14 @@ Global
{0A948D71-99C5-43E9-BACB-B0BA59EA25B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0A948D71-99C5-43E9-BACB-B0BA59EA25B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0A948D71-99C5-43E9-BACB-B0BA59EA25B4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D7FE3E0F-3FE0-4F87-A2F5-24F1454D84C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D7FE3E0F-3FE0-4F87-A2F5-24F1454D84C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D7FE3E0F-3FE0-4F87-A2F5-24F1454D84C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D7FE3E0F-3FE0-4F87-A2F5-24F1454D84C0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DA5F1FF9-4259-4C54-B443-85CFA226EE6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DA5F1FF9-4259-4C54-B443-85CFA226EE6A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DA5F1FF9-4259-4C54-B443-85CFA226EE6A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DA5F1FF9-4259-4C54-B443-85CFA226EE6A}.Release|Any CPU.Build.0 = Release|Any CPU
{9AE1B827-21AC-4063-AB22-C8804B7F931E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9AE1B827-21AC-4063-AB22-C8804B7F931E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9AE1B827-21AC-4063-AB22-C8804B7F931E}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -764,6 +778,9 @@ Global
{9D6AEF22-221F-4F4B-B335-A4BA510F002C} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{5BF0C3B8-E595-4940-AB30-2DA206C2F085} = {9D6AEF22-221F-4F4B-B335-A4BA510F002C}
{0A948D71-99C5-43E9-BACB-B0BA59EA25B4} = {5BF0C3B8-E595-4940-AB30-2DA206C2F085}
+ {9CCA131B-DE95-4D44-8788-C3CAE28574CD} = {9B9E3891-2366-4253-A952-D08BCEB71098}
+ {D7FE3E0F-3FE0-4F87-A2F5-24F1454D84C0} = {9CCA131B-DE95-4D44-8788-C3CAE28574CD}
+ {DA5F1FF9-4259-4C54-B443-85CFA226EE6A} = {9CCA131B-DE95-4D44-8788-C3CAE28574CD}
{9AE1B827-21AC-4063-AB22-C8804B7F931E} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml
index 9a11864491..00ec23467d 100644
--- a/api/Avalonia.nupkg.xml
+++ b/api/Avalonia.nupkg.xml
@@ -1075,6 +1075,18 @@
baseline/netstandard2.0/Avalonia.Dialogs.dll
target/netstandard2.0/Avalonia.Dialogs.dll
+
+ CP0006
+ M:Avalonia.Platform.IAssetLoader.InvalidateAssemblyCache
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0006
+ M:Avalonia.Platform.IAssetLoader.InvalidateAssemblyCache(System.String)
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
CP0006
P:Avalonia.Media.IRadialGradientBrush.RadiusX
diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/App.axaml b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/App.axaml
new file mode 100644
index 0000000000..856100f059
--- /dev/null
+++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/App.axaml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/App.axaml.cs b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/App.axaml.cs
new file mode 100644
index 0000000000..7dfdcded75
--- /dev/null
+++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/App.axaml.cs
@@ -0,0 +1,23 @@
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Markup.Xaml;
+
+namespace UnloadableAssemblyLoadContext;
+
+public partial class App : Application
+{
+ public override void Initialize()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ public override void OnFrameworkInitializationCompleted()
+ {
+ if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+ {
+ desktop.MainWindow = new MainWindow();
+ }
+
+ base.OnFrameworkInitializationCompleted();
+ }
+}
diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/AssemblyLoadContextH.cs b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/AssemblyLoadContextH.cs
new file mode 100644
index 0000000000..780be4d4b8
--- /dev/null
+++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/AssemblyLoadContextH.cs
@@ -0,0 +1,57 @@
+#region
+
+using System;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.Loader;
+using Avalonia;
+using Avalonia.Platform;
+using Avalonia.Styling;
+
+#endregion
+
+namespace UnloadableAssemblyLoadContext;
+
+public class AssemblyLoadContextH : AssemblyLoadContext
+{
+ private readonly AssemblyDependencyResolver _resolver;
+
+ public AssemblyLoadContextH(string pluginPath, string name) : base(isCollectible: true, name: name)
+ {
+ _resolver = new AssemblyDependencyResolver(pluginPath);
+ Unloading += (sender) =>
+ {
+ AvaloniaPropertyRegistry.Instance.UnregisterByModule(sender.Assemblies.First().DefinedTypes);
+ Application.Current.Styles.Remove(MainWindow.Style);
+ AssetLoader.InvalidateAssemblyCache(sender.Assemblies.First().GetName().Name);
+ MainWindow.Style= null;
+ };
+ }
+
+ protected override Assembly Load(AssemblyName assemblyName)
+ {
+ var assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
+ if (assemblyPath != null)
+ {
+ if (assemblyPath.EndsWith("WinRT.Runtime.dll") || assemblyPath.EndsWith("Microsoft.Windows.SDK.NET.dll")|| assemblyPath.EndsWith("Avalonia.Controls.dll")|| assemblyPath.EndsWith("Avalonia.Base.dll")|| assemblyPath.EndsWith("Avalonia.Markup.Xaml.dll"))
+ {
+ return null;
+ }
+
+ return LoadFromAssemblyPath(assemblyPath);
+ }
+
+ return null;
+ }
+
+ protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
+ {
+ var libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
+ if (libraryPath != null)
+ {
+ return LoadUnmanagedDllFromPath(libraryPath);
+ }
+
+ return IntPtr.Zero;
+ }
+}
diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/MainWindow.axaml b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/MainWindow.axaml
new file mode 100644
index 0000000000..54e9585a57
--- /dev/null
+++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/MainWindow.axaml
@@ -0,0 +1,9 @@
+
+ Welcome to Avalonia!
+
diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/MainWindow.axaml.cs b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/MainWindow.axaml.cs
new file mode 100644
index 0000000000..959740da59
--- /dev/null
+++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/MainWindow.axaml.cs
@@ -0,0 +1,134 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Markup.Xaml;
+using Avalonia.Markup.Xaml.Styling;
+using Avalonia.Markup.Xaml.XamlIl.Runtime;
+using Avalonia.Platform;
+using Avalonia.Platform.Internal;
+using Avalonia.Styling;
+using Avalonia.Threading;
+using Avalonia.VisualTree;
+
+namespace UnloadableAssemblyLoadContext;
+
+public partial class MainWindow : Window
+{
+ public MainWindow()
+ {
+ InitializeComponent();
+ }
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ if (Debugger.IsAttached)
+ {
+ this.AttachDevTools();
+ }
+ }
+ private PlugTool _plugTool;
+ protected override void OnOpened(EventArgs e)
+ {
+ base.OnOpened(e);
+ test();
+ //Content = _plugTool.FindControl("UnloadableAssemblyLoadContextPlug.TestControl");
+
+
+ }
+ public T? GetChildOfType(Control control)
+ where T : Control
+ {
+ var queue = new Queue();
+ queue.Enqueue(control);
+
+ while (queue.Count > 0)
+ {
+ var currentControl = queue.Dequeue();
+ foreach (var child in currentControl.GetVisualChildren())
+ {
+ var childControl = child as Control;
+ if (childControl != null)
+ {
+ var childControlStyles = childControl.Styles;
+ if (childControlStyles.Count>1)
+ {
+
+ }
+ queue.Enqueue(childControl);
+ }
+ }
+ }
+
+ return null;
+ }
+ protected override void OnClosed(EventArgs e)
+ {
+ base.OnClosed(e);
+ GetChildOfType(this);
+
+
+ Thread.CurrentThread.IsBackground = false;
+ var weakReference = _plugTool.Unload();
+ while (weakReference.IsAlive)
+ {
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ Thread.Sleep(100);
+ }
+
+ Console.WriteLine("Done");
+
+
+ }
+
+ public static IStyle Style;
+ public void test(){
+
+ //Notice : 你可以删除UnloadableAssemblyLoadContextPlug.dll所在文件夹中有关Avalonia的所有Dll,但这不是必须的
+ //Notice : You can delete all Dlls about Avalonia in the folder where UnloadableAssemblyLoadContextPlug.dll is located, but this is not necessary
+ FileInfo fileInfo = new FileInfo("..\\..\\..\\..\\UnloadableAssemblyLoadContextPlug\\bin\\Debug\\net7.0\\UnloadableAssemblyLoadContextPlug.dll");
+ var AssemblyLoadContextH = new AssemblyLoadContextH(fileInfo.FullName,"test");
+
+ var assembly = AssemblyLoadContextH.LoadFromAssemblyPath(fileInfo.FullName);
+ var assemblyDescriptorResolver =
+ _plugTool=new PlugTool();
+ _plugTool.AssemblyLoadContextH = AssemblyLoadContextH;
+
+ var styles = new Styles();
+ var styleInclude = new StyleInclude(new Uri("avares://UnloadableAssemblyLoadContextPlug", UriKind.Absolute));
+ styleInclude.Source=new Uri("ControlStyle.axaml", UriKind.Relative);
+ styles.Add(styleInclude);
+ Style = styles;
+ Application.Current.Styles.Add(styles);
+ foreach (var type in assembly.GetTypes())
+ {
+ if (type.FullName=="AvaloniaPlug.Window1")
+ {
+ //创建type实例
+ Window instance = (Window)type.GetConstructor( new Type[0]).Invoke(null);
+
+ Dispatcher.UIThread.InvokeAsync(() =>
+ {
+ instance.Show();
+ instance.Close();
+
+ }).Wait();
+
+ instance = null;
+
+ //instance.Show();
+ }
+
+ }
+
+ }
+
+
+}
diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/PlugTool.cs b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/PlugTool.cs
new file mode 100644
index 0000000000..fb5cc28265
--- /dev/null
+++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/PlugTool.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Linq;
+using Avalonia.Controls;
+
+namespace UnloadableAssemblyLoadContext;
+
+public class PlugTool
+{
+ public AssemblyLoadContextH AssemblyLoadContextH;
+ public WeakReference Unload()
+ {
+ var weakReference = new WeakReference(AssemblyLoadContextH);
+ AssemblyLoadContextH.Unload();
+ AssemblyLoadContextH = null;
+ return weakReference;
+ }
+
+ public Control? FindControl(string type)
+ {
+ var type1 = AssemblyLoadContextH.Assemblies.
+ FirstOrDefault(x => x.GetName().Name == "UnloadableAssemblyLoadContextPlug")?.
+ GetType(type);
+ if (type1.IsSubclassOf(typeof(Control)))
+ {
+ var constructorInfo = type1.GetConstructor( Type.EmptyTypes).Invoke(null) as Control;
+ return constructorInfo;
+ }
+
+ return null;
+ }
+}
diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/Program.cs b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/Program.cs
new file mode 100644
index 0000000000..1aced7f099
--- /dev/null
+++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/Program.cs
@@ -0,0 +1,21 @@
+using System;
+using Avalonia;
+
+namespace UnloadableAssemblyLoadContext;
+
+class Program
+{
+ // Initialization code. Don't use any Avalonia, third-party APIs or any
+ // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
+ // yet and stuff might break.
+ [STAThread]
+ public static void Main(string[] args) => BuildAvaloniaApp()
+ .StartWithClassicDesktopLifetime(args);
+
+ // Avalonia configuration, don't remove; also used by visual designer.
+ public static AppBuilder BuildAvaloniaApp()
+ => AppBuilder.Configure()
+ .UsePlatformDetect()
+ .WithInterFont()
+ .LogToTrace();
+}
diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/Styles1.axaml b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/Styles1.axaml
new file mode 100644
index 0000000000..958ee15a50
--- /dev/null
+++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/Styles1.axaml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext.csproj b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext.csproj
new file mode 100644
index 0000000000..e39a35a294
--- /dev/null
+++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext.csproj
@@ -0,0 +1,38 @@
+
+
+ WinExe
+ net7.0
+ enable
+ true
+ app.manifest
+ true
+ true
+
+
+
+
+ %(Filename)
+
+
+ Designer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/app.manifest b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/app.manifest
new file mode 100644
index 0000000000..64334402c8
--- /dev/null
+++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/app.manifest
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/ControlStyle.axaml b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/ControlStyle.axaml
new file mode 100644
index 0000000000..888e73c2bb
--- /dev/null
+++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/ControlStyle.axaml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Program.cs b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Program.cs
new file mode 100644
index 0000000000..60c37ec32f
--- /dev/null
+++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Program.cs
@@ -0,0 +1,9 @@
+namespace AvaloniaPlug;
+
+class Program
+{
+ // Initialization code. Don't use any Avalonia, third-party APIs or any
+ // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
+ // yet and stuff might break.
+ private static string test = "23";
+}
\ No newline at end of file
diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/TestControl.axaml b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/TestControl.axaml
new file mode 100644
index 0000000000..a0dfedaa62
--- /dev/null
+++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/TestControl.axaml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/TestControl.axaml.cs b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/TestControl.axaml.cs
new file mode 100644
index 0000000000..01fdf5a832
--- /dev/null
+++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/TestControl.axaml.cs
@@ -0,0 +1,10 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+
+namespace UnloadableAssemblyLoadContextPlug;
+
+public class TestControl : TemplatedControl
+{
+}
+
diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/UnloadableAssemblyLoadContextPlug.csproj b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/UnloadableAssemblyLoadContextPlug.csproj
new file mode 100644
index 0000000000..564c169818
--- /dev/null
+++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/UnloadableAssemblyLoadContextPlug.csproj
@@ -0,0 +1,51 @@
+
+
+ net7.0
+ enable
+ true
+ app.manifest
+ true
+ UnloadableAssemblyLoadContextPlug
+ UnloadableAssemblyLoadContextPlug
+
+
+
+
+
+
+
+ %(Filename)
+
+
+ Designer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Window1.axaml b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Window1.axaml
new file mode 100644
index 0000000000..83a389f31e
--- /dev/null
+++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Window1.axaml
@@ -0,0 +1,12 @@
+
+
+
diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Window1.axaml.cs b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Window1.axaml.cs
new file mode 100644
index 0000000000..8236d1a338
--- /dev/null
+++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Window1.axaml.cs
@@ -0,0 +1,22 @@
+using System.Diagnostics;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using AvaloniaPlug;
+
+namespace UnloadableAssemblyLoadContextPlug;
+
+public partial class Window1 : Window
+{
+ public Window1()
+ {
+ InitializeComponent();
+ DataContext=new Window1ViewModel();
+ }
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+
+
+ }
+}
diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Window1ViewModel.cs b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Window1ViewModel.cs
new file mode 100644
index 0000000000..7d6bee2c61
--- /dev/null
+++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Window1ViewModel.cs
@@ -0,0 +1,8 @@
+
+
+namespace UnloadableAssemblyLoadContextPlug;
+
+public partial class Window1ViewModel
+{
+ public string Text { get; set; } = "12";
+}
diff --git a/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/app.manifest b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/app.manifest
new file mode 100644
index 0000000000..39c2b00338
--- /dev/null
+++ b/samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/app.manifest
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs
index a56f3f098c..9a45338bf4 100644
--- a/src/Avalonia.Base/AvaloniaProperty.cs
+++ b/src/Avalonia.Base/AvaloniaProperty.cs
@@ -223,6 +223,11 @@ namespace Avalonia
return !(a == b);
}
+ public void Unregister(Type type)
+ {
+ _metadata.Remove(type);
+ _metadataCache.Remove(type);
+ }
///
/// Registers a .
///
diff --git a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs
index 8e6f7b0983..a772f32bf0 100644
--- a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs
+++ b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs
@@ -1,7 +1,10 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Reflection;
using System.Runtime.CompilerServices;
+using System.Threading;
namespace Avalonia
{
@@ -38,6 +41,53 @@ namespace Avalonia
///
internal IReadOnlyCollection Properties => _properties.Values;
+ private object _unregisteringLocker = new object();
+ ///
+ /// Unregister alls registered on types
+ ///
+ ///
+ ///
+ public bool UnregisterByModule(IEnumerable types)
+ {
+ _ = types ?? throw new ArgumentNullException(nameof(types));
+
+ lock (_unregisteringLocker)
+ {
+ try
+ {
+ foreach (var type in types)
+ {
+ Unregister(_registered, type);
+ Unregister(_attached, type);
+ Unregister(_direct, type);
+ Unregister(_registeredCache,type);
+ Unregister(_attachedCache,type);
+ Unregister(_directCache,type);
+ Unregister(_inheritedCache,type);
+ }
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ private void Unregister( Dictionary> dictionary,Type type)
+ {
+ dictionary.Remove(type);
+ }
+ private void Unregister( Dictionary> dictionary,Type type)
+ {
+ foreach (var keyValuePair in dictionary)
+ {
+ foreach (var key in keyValuePair.Value)
+ {
+ key.Value.Unregister(type);
+ }
+ }
+ }
///
/// Gets all non-attached s registered on a type.
///
@@ -47,7 +97,6 @@ namespace Avalonia
public IReadOnlyList GetRegistered(Type type)
{
_ = type ?? throw new ArgumentNullException(nameof(type));
-
if (_registeredCache.TryGetValue(type, out var result))
{
return result;
@@ -55,21 +104,20 @@ namespace Avalonia
var t = type;
result = new List();
-
- while (t != null)
+ lock (_unregisteringLocker)
{
- // Ensure the type's static ctor has been run.
- RuntimeHelpers.RunClassConstructor(t.TypeHandle);
-
- if (_registered.TryGetValue(t, out var registered))
+ while (t != null)
{
- result.AddRange(registered.Values);
+ // Ensure the type's static ctor has been run.
+ RuntimeHelpers.RunClassConstructor(t.TypeHandle);
+ if (_registered.TryGetValue(t, out var registered))
+ {
+ result.AddRange(registered.Values);
+ }
+ t = t.BaseType;
}
-
- t = t.BaseType;
+ _registeredCache.Add(type, result);
}
-
- _registeredCache.Add(type, result);
return result;
}
@@ -81,7 +129,6 @@ namespace Avalonia
public IReadOnlyList GetRegisteredAttached(Type type)
{
_ = type ?? throw new ArgumentNullException(nameof(type));
-
if (_attachedCache.TryGetValue(type, out var result))
{
return result;
@@ -89,18 +136,18 @@ namespace Avalonia
var t = type;
result = new List();
-
- while (t != null)
+ lock (_unregisteringLocker)
{
- if (_attached.TryGetValue(t, out var attached))
+ while (t != null)
{
- result.AddRange(attached.Values);
+ if (_attached.TryGetValue(t, out var attached))
+ {
+ result.AddRange(attached.Values);
+ }
+ t = t.BaseType;
}
-
- t = t.BaseType;
+ _attachedCache.Add(type, result);
}
-
- _attachedCache.Add(type, result);
return result;
}
@@ -112,7 +159,6 @@ namespace Avalonia
public IReadOnlyList GetRegisteredDirect(Type type)
{
_ = type ?? throw new ArgumentNullException(nameof(type));
-
if (_directCache.TryGetValue(type, out var result))
{
return result;
@@ -120,18 +166,18 @@ namespace Avalonia
var t = type;
result = new List();
-
- while (t != null)
+ lock (_unregisteringLocker)
{
- if (_direct.TryGetValue(t, out var direct))
+ while (t != null)
{
- result.AddRange(direct.Values);
+ if (_direct.TryGetValue(t, out var direct))
+ {
+ result.AddRange(direct.Values);
+ }
+ t = t.BaseType;
}
-
- t = t.BaseType;
+ _directCache.Add(type, result);
}
-
- _directCache.Add(type, result);
return result;
}
@@ -143,7 +189,6 @@ namespace Avalonia
public IReadOnlyList GetRegisteredInherited(Type type)
{
_ = type ?? throw new ArgumentNullException(nameof(type));
-
if (_inheritedCache.TryGetValue(type, out var result))
{
return result;
@@ -182,7 +227,11 @@ namespace Avalonia
}
}
- _inheritedCache.Add(type, result);
+ lock (_unregisteringLocker)
+ {
+ _inheritedCache.Add(type, result);
+ }
+
return result;
}
@@ -211,7 +260,7 @@ namespace Avalonia
DirectPropertyBase property)
{
return FindRegisteredDirect(o, property) ??
- throw new ArgumentException($"Property '{property.Name} not registered on '{o.GetType()}");
+ throw new ArgumentException($"Property '{property.Name} not registered on '{o.GetType()}");
}
///
@@ -371,41 +420,44 @@ namespace Avalonia
{
_ = type ?? throw new ArgumentNullException(nameof(type));
_ = property ?? throw new ArgumentNullException(nameof(property));
-
- if (!_registered.TryGetValue(type, out var inner))
+ lock (_unregisteringLocker)
{
- inner = new Dictionary();
- inner.Add(property.Id, property);
- _registered.Add(type, inner);
- }
- else if (!inner.ContainsKey(property.Id))
- {
- inner.Add(property.Id, property);
- }
-
- if (property.IsDirect)
- {
- if (!_direct.TryGetValue(type, out inner))
+ if (!_registered.TryGetValue(type, out var inner))
{
inner = new Dictionary();
inner.Add(property.Id, property);
- _direct.Add(type, inner);
+ _registered.Add(type, inner);
}
else if (!inner.ContainsKey(property.Id))
{
inner.Add(property.Id, property);
}
- _directCache.Clear();
- }
+ if (property.IsDirect)
+ {
+ if (!_direct.TryGetValue(type, out inner))
+ {
+ inner = new Dictionary();
+ inner.Add(property.Id, property);
+ _direct.Add(type, inner);
+ }
+ else if (!inner.ContainsKey(property.Id))
+ {
+ inner.Add(property.Id, property);
+ }
- if (!_properties.ContainsKey(property.Id))
- {
- _properties.Add(property.Id, property);
+ _directCache.Clear();
+ }
+
+ if (!_properties.ContainsKey(property.Id))
+ {
+ _properties.Add(property.Id, property);
+ }
+
+ _registeredCache.Clear();
+ _inheritedCache.Clear();
}
- _registeredCache.Clear();
- _inheritedCache.Clear();
}
///
@@ -422,26 +474,29 @@ namespace Avalonia
{
_ = type ?? throw new ArgumentNullException(nameof(type));
_ = property ?? throw new ArgumentNullException(nameof(property));
-
+
if (!property.IsAttached)
{
throw new InvalidOperationException(
"Cannot register a non-attached property as attached.");
}
-
- if (!_attached.TryGetValue(type, out var inner))
- {
- inner = new Dictionary();
- inner.Add(property.Id, property);
- _attached.Add(type, inner);
- }
- else
+ lock (_unregisteringLocker)
{
- inner.Add(property.Id, property);
- }
+ if (!_attached.TryGetValue(type, out var inner))
+ {
+ inner = new Dictionary();
+ inner.Add(property.Id, property);
+ _attached.Add(type, inner);
+ }
+ else
+ {
+ inner.Add(property.Id, property);
+ }
- _attachedCache.Clear();
- _inheritedCache.Clear();
+ _attachedCache.Clear();
+ _inheritedCache.Clear();
+ }
+
}
}
}
diff --git a/src/Avalonia.Base/Platform/AssetLoader.cs b/src/Avalonia.Base/Platform/AssetLoader.cs
index 854610f1c9..a6a65804c7 100644
--- a/src/Avalonia.Base/Platform/AssetLoader.cs
+++ b/src/Avalonia.Base/Platform/AssetLoader.cs
@@ -33,6 +33,12 @@ public static class AssetLoader
///
public static IEnumerable GetAssets(Uri uri, Uri? baseUri)
=> GetAssetLoader().GetAssets(uri, baseUri);
+
+ ///
+ public static void InvalidateAssemblyCache(string name) => GetAssetLoader().InvalidateAssemblyCache(name);
+ ///
+ public static void InvalidateAssemblyCache() => GetAssetLoader().InvalidateAssemblyCache();
+
#endif
internal static void RegisterResUriParsers()
diff --git a/src/Avalonia.Base/Platform/IAssetLoader.cs b/src/Avalonia.Base/Platform/IAssetLoader.cs
index f1ce624c70..cdd4870e0f 100644
--- a/src/Avalonia.Base/Platform/IAssetLoader.cs
+++ b/src/Avalonia.Base/Platform/IAssetLoader.cs
@@ -77,5 +77,15 @@ namespace Avalonia.Platform
/// The base URI.
/// All matching assets as a tuple of the absolute path to the asset and the assembly containing the asset
IEnumerable GetAssets(Uri uri, Uri? baseUri);
+
+ ///
+ /// Removes the assembly from the cache.
+ ///
+ /// The Assemblies.First().GetName().Name
+ void InvalidateAssemblyCache(string name);
+ ///
+ /// Removes all assemblies from the cache.
+ ///
+ void InvalidateAssemblyCache();
}
}
diff --git a/src/Avalonia.Base/Platform/Internal/AssemblyDescriptorResolver.cs b/src/Avalonia.Base/Platform/Internal/AssemblyDescriptorResolver.cs
index b12130b1f7..8360feff4a 100644
--- a/src/Avalonia.Base/Platform/Internal/AssemblyDescriptorResolver.cs
+++ b/src/Avalonia.Base/Platform/Internal/AssemblyDescriptorResolver.cs
@@ -9,6 +9,9 @@ namespace Avalonia.Platform.Internal;
internal interface IAssemblyDescriptorResolver
{
IAssemblyDescriptor GetAssembly(string name);
+ void InvalidateAssemblyCache(string name);
+ void InvalidateAssemblyCache();
+
}
internal class AssemblyDescriptorResolver: IAssemblyDescriptorResolver
@@ -44,4 +47,13 @@ internal class AssemblyDescriptorResolver: IAssemblyDescriptorResolver
return rv;
}
+ public void InvalidateAssemblyCache(string name)
+ {
+ _assemblyNameCache.Remove(name);
+ }
+
+ public void InvalidateAssemblyCache()
+ {
+ _assemblyNameCache.Clear();
+ }
}
diff --git a/src/Avalonia.Base/Platform/StandardAssetLoader.cs b/src/Avalonia.Base/Platform/StandardAssetLoader.cs
index 1d9363c70d..a55ac1428b 100644
--- a/src/Avalonia.Base/Platform/StandardAssetLoader.cs
+++ b/src/Avalonia.Base/Platform/StandardAssetLoader.cs
@@ -155,6 +155,16 @@ public class StandardAssetLoader : IAssetLoader
return Enumerable.Empty();
}
+ public void InvalidateAssemblyCache(string name)
+ {
+ _assemblyDescriptorResolver.InvalidateAssemblyCache(name);
+ }
+
+ public void InvalidateAssemblyCache()
+ {
+ _assemblyDescriptorResolver.InvalidateAssemblyCache();
+ }
+
public static void RegisterResUriParsers() => AssetLoader.RegisterResUriParsers();
private bool TryGetAsset(Uri uri, Uri? baseUri, [NotNullWhen(true)] out IAssetDescriptor? assetDescriptor)
diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs
index 70e531a789..ee8bd951d0 100644
--- a/src/Avalonia.Controls/TopLevel.cs
+++ b/src/Avalonia.Controls/TopLevel.cs
@@ -312,7 +312,6 @@ namespace Avalonia.Controls
}
});
}
-
///
/// Fired when the window is opened.
///
@@ -714,6 +713,7 @@ namespace Avalonia.Controls
OnClosed(EventArgs.Empty);
LayoutManager.Dispose();
+ _platformImplBindings.Clear();
}
///
diff --git a/tests/Avalonia.UnitTests/MockAssetLoader.cs b/tests/Avalonia.UnitTests/MockAssetLoader.cs
index 6fe8cd54f7..cf2d327e54 100644
--- a/tests/Avalonia.UnitTests/MockAssetLoader.cs
+++ b/tests/Avalonia.UnitTests/MockAssetLoader.cs
@@ -45,6 +45,16 @@ namespace Avalonia.UnitTests
x => x.GetUnescapeAbsolutePath().IndexOf(absPath, StringComparison.Ordinal) >= 0);
}
+ public void InvalidateAssemblyCache(string name)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void InvalidateAssemblyCache()
+ {
+ throw new NotImplementedException();
+ }
+
public void SetDefaultAssembly(Assembly asm)
{
throw new NotImplementedException();