Răsfoiți Sursa

Enhancement Allow unload AssemblyLoadContext which contains Avalonia content #13935 (#13974)

* Try fix #13935

* Fix

* Fix

* add sample

* Fix

* try load Style by reflection

* try

* Fixed an error when registering properties when uninstalling assemblies

* Allowed to delete the IAssemblyDescriptorResolver StandardAssetLoader _assemblyNameCache

* Resolving merge conflicts

* Fix

* Add exegesis

* optimize

* fix

* Resolving merge conflicts

* nuke
MakesYT 1 an în urmă
părinte
comite
299c16da55
30 a modificat fișierele cu 720 adăugiri și 70 ștergeri
  1. 2 0
      Avalonia.Desktop.slnf
  2. 18 1
      Avalonia.sln
  3. 12 0
      api/Avalonia.nupkg.xml
  4. 10 0
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/App.axaml
  5. 23 0
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/App.axaml.cs
  6. 57 0
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/AssemblyLoadContextH.cs
  7. 9 0
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/MainWindow.axaml
  8. 134 0
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/MainWindow.axaml.cs
  9. 31 0
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/PlugTool.cs
  10. 21 0
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/Program.cs
  11. 11 0
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/Styles1.axaml
  12. 38 0
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext.csproj
  13. 18 0
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/app.manifest
  14. 12 0
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/ControlStyle.axaml
  15. 9 0
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Program.cs
  16. 17 0
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/TestControl.axaml
  17. 10 0
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/TestControl.axaml.cs
  18. 51 0
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/UnloadableAssemblyLoadContextPlug.csproj
  19. 12 0
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Window1.axaml
  20. 22 0
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Window1.axaml.cs
  21. 8 0
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Window1ViewModel.cs
  22. 18 0
      samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/app.manifest
  23. 5 0
      src/Avalonia.Base/AvaloniaProperty.cs
  24. 123 68
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  25. 6 0
      src/Avalonia.Base/Platform/AssetLoader.cs
  26. 10 0
      src/Avalonia.Base/Platform/IAssetLoader.cs
  27. 12 0
      src/Avalonia.Base/Platform/Internal/AssemblyDescriptorResolver.cs
  28. 10 0
      src/Avalonia.Base/Platform/StandardAssetLoader.cs
  29. 1 1
      src/Avalonia.Controls/TopLevel.cs
  30. 10 0
      tests/Avalonia.UnitTests/MockAssetLoader.cs

+ 2 - 0
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",

+ 18 - 1
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

+ 12 - 0
api/Avalonia.nupkg.xml

@@ -1075,6 +1075,18 @@
     <Left>baseline/netstandard2.0/Avalonia.Dialogs.dll</Left>
     <Right>target/netstandard2.0/Avalonia.Dialogs.dll</Right>
   </Suppression>
+  <Suppression>
+    <DiagnosticId>CP0006</DiagnosticId>
+    <Target>M:Avalonia.Platform.IAssetLoader.InvalidateAssemblyCache</Target>
+    <Left>baseline/netstandard2.0/Avalonia.Base.dll</Left>
+    <Right>target/netstandard2.0/Avalonia.Base.dll</Right>
+  </Suppression>
+  <Suppression>
+    <DiagnosticId>CP0006</DiagnosticId>
+    <Target>M:Avalonia.Platform.IAssetLoader.InvalidateAssemblyCache(System.String)</Target>
+    <Left>baseline/netstandard2.0/Avalonia.Base.dll</Left>
+    <Right>target/netstandard2.0/Avalonia.Base.dll</Right>
+  </Suppression>
   <Suppression>
     <DiagnosticId>CP0006</DiagnosticId>
     <Target>P:Avalonia.Media.IRadialGradientBrush.RadiusX</Target>

+ 10 - 0
samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/App.axaml

@@ -0,0 +1,10 @@
+<Application xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Class="UnloadableAssemblyLoadContext.App"
+             RequestedThemeVariant="Default">
+             <!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
+
+    <Application.Styles>
+        <FluentTheme />
+    </Application.Styles>
+</Application>

+ 23 - 0
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();
+    }
+}

+ 57 - 0
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;
+    }
+}

+ 9 - 0
samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/MainWindow.axaml

@@ -0,0 +1,9 @@
+<Window xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+        x:Class="UnloadableAssemblyLoadContext.MainWindow"
+        Title="UnloadableAssemblyLoadContext">
+    Welcome to Avalonia!
+</Window>

+ 134 - 0
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<T>(Control control)
+        where T : Control
+    {
+        var queue = new Queue<Control>();
+        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<Control>(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();
+            }
+
+        }
+        
+    }
+   
+    
+}

+ 31 - 0
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;
+    }
+}

+ 21 - 0
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<App>()
+                     .UsePlatformDetect()
+                     .WithInterFont()
+                     .LogToTrace();
+}

+ 11 - 0
samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/Styles1.axaml

@@ -0,0 +1,11 @@
+<Styles xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+  <Design.PreviewWith>
+    <Border Padding="20">
+      <!-- Add Controls for Previewer Here -->
+    </Border>
+  </Design.PreviewWith>
+
+  <!-- Add Styles Here -->
+</Styles>
+

+ 38 - 0
samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext.csproj

@@ -0,0 +1,38 @@
+<Project Sdk="Microsoft.NET.Sdk">
+    <PropertyGroup>
+        <OutputType>WinExe</OutputType>
+        <TargetFramework>net7.0</TargetFramework>
+        <Nullable>enable</Nullable>
+        <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
+        <ApplicationManifest>app.manifest</ApplicationManifest>
+        <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
+      <IncludeAvaloniaGenerators>true</IncludeAvaloniaGenerators>
+    </PropertyGroup>
+
+  <ItemGroup>
+    <Compile Update="**\*.xaml.cs">
+      <DependentUpon>%(Filename)</DependentUpon>
+    </Compile>
+    <AvaloniaResource Include="**\*.xaml">
+      <SubType>Designer</SubType>
+    </AvaloniaResource>
+    <AvaloniaResource Include="Assets\*" />
+    <AvaloniaResource Include="Assets\Fonts\*" />
+  </ItemGroup>
+  <ItemGroup>
+    <Folder Include="Models\"/>
+    <AvaloniaResource Include="Assets\**"/>
+  </ItemGroup>
+  <ImportGroup>
+    <Import Project="..\..\..\build\BuildTargets.targets" Condition="Exists('..\..\..\build\BuildTargets.targets')" />
+    <Import Project="..\..\..\build\SourceGenerators.props" />
+  </ImportGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" />
+    <ProjectReference Include="..\..\..\src\Avalonia.Base\Avalonia.Base.csproj" />
+    <ProjectReference Include="..\..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" />
+    <ProjectReference Include="..\..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
+    <ProjectReference Include="..\..\..\src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj" />
+    <ProjectReference Include="..\..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
+    </ItemGroup>
+</Project>

+ 18 - 0
samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContext/app.manifest

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
+  <!-- This manifest is used on Windows only.
+       Don't remove it as it might cause problems with window transparency and embedded controls.
+       For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
+  <assemblyIdentity version="1.0.0.0" name="AvaloniaApplication2.Desktop"/>
+
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!-- A list of the Windows versions that this application has been tested on
+           and is designed to work with. Uncomment the appropriate elements
+           and Windows will automatically select the most compatible environment. -->
+
+      <!-- Windows 10 -->
+      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
+    </application>
+  </compatibility>
+</assembly>

+ 12 - 0
samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/ControlStyle.axaml

@@ -0,0 +1,12 @@
+<Styles xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        >
+  <Design.PreviewWith>
+    <Border Padding="20">
+      <!-- Add Controls for Previewer Here -->
+    </Border>
+  </Design.PreviewWith>
+
+  <StyleInclude Source="TestControl.axaml"></StyleInclude>
+</Styles>
+

+ 9 - 0
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";
+}

+ 17 - 0
samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/TestControl.axaml

@@ -0,0 +1,17 @@
+<Styles xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:controls="using:UnloadableAssemblyLoadContextPlug">
+  <Design.PreviewWith>
+    <controls:TestControl />
+  </Design.PreviewWith>
+
+  <Style Selector="controls|TestControl">
+    <!-- Set Defaults -->
+    <Setter Property="Template">
+      <ControlTemplate>
+        <TextBlock Text="Templated Control" />
+      </ControlTemplate>
+    </Setter>
+  </Style>
+</Styles>
+

+ 10 - 0
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
+{
+}
+

+ 51 - 0
samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/UnloadableAssemblyLoadContextPlug.csproj

@@ -0,0 +1,51 @@
+<Project Sdk="Microsoft.NET.Sdk">
+    <PropertyGroup>
+        <TargetFramework>net7.0</TargetFramework>
+        <Nullable>enable</Nullable>
+        <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
+        <ApplicationManifest>app.manifest</ApplicationManifest>
+        <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
+      <AssemblyName>UnloadableAssemblyLoadContextPlug</AssemblyName>
+      <RootNamespace>UnloadableAssemblyLoadContextPlug</RootNamespace>
+    </PropertyGroup>
+
+    <ItemGroup>
+        <AvaloniaResource Include="Assets\**"/>
+    </ItemGroup>
+  <ItemGroup>
+    <Compile Update="**\*.xaml.cs">
+      <DependentUpon>%(Filename)</DependentUpon>
+    </Compile>
+    <AvaloniaResource Include="**\*.xaml">
+      <SubType>Designer</SubType>
+    </AvaloniaResource>
+    <AvaloniaResource Include="Assets\*" />
+    <AvaloniaResource Include="Assets\Fonts\*" />
+  </ItemGroup>
+
+  <ImportGroup>
+    <Import Project="..\..\..\build\BuildTargets.targets" Condition="Exists('..\..\..\build\BuildTargets.targets')" />
+    <Import Project="..\..\..\build\SourceGenerators.props" />
+  </ImportGroup>
+
+
+
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" />
+    <ProjectReference Include="..\..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" />
+    <ProjectReference Condition="'$(Configuration)' == 'Debug'" Include="..\..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
+    <ProjectReference Include="..\..\..\src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj" />
+    <ProjectReference Include="..\..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj" />
+    <ProjectReference Include="..\..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" />
+    <ProjectReference Include="..\..\..\src\Markup\Avalonia.Markup.Xaml.Loader\Avalonia.Markup.Xaml.Loader.csproj" />
+  </ItemGroup>
+
+
+    <ItemGroup>
+      <UpToDateCheckInput Remove="Views\MainWindow.axaml" />
+    </ItemGroup>
+
+
+  
+</Project>

+ 12 - 0
samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Window1.axaml

@@ -0,0 +1,12 @@
+<Window xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        xmlns:avaloniaPlug="clr-namespace:AvaloniaPlug"
+        xmlns:unloadableAssemblyLoadContextPlug="clr-namespace:UnloadableAssemblyLoadContextPlug"
+        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+        x:Class="UnloadableAssemblyLoadContextPlug.Window1"
+        x:DataType="unloadableAssemblyLoadContextPlug:Window1ViewModel"
+        Title="Window1">
+    <TextBlock Text="{Binding Text }" HorizontalAlignment="Center" VerticalAlignment="Center" />
+</Window>

+ 22 - 0
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);
+      
+        
+    }
+}

+ 8 - 0
samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/Window1ViewModel.cs

@@ -0,0 +1,8 @@
+
+
+namespace UnloadableAssemblyLoadContextPlug;
+
+public partial class Window1ViewModel 
+{
+    public string Text { get; set; } = "12";
+}

+ 18 - 0
samples/UnloadableAssemblyLoadContext/UnloadableAssemblyLoadContextPlug/app.manifest

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
+  <!-- This manifest is used on Windows only.
+       Don't remove it as it might cause problems with window transparency and embedded controls.
+       For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
+  <assemblyIdentity version="1.0.0.0" name="AvaloniaPlug.Desktop"/>
+
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!-- A list of the Windows versions that this application has been tested on
+           and is designed to work with. Uncomment the appropriate elements
+           and Windows will automatically select the most compatible environment. -->
+
+      <!-- Windows 10 -->
+      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
+    </application>
+  </compatibility>
+</assembly>

+ 5 - 0
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);
+        }
         /// <summary>
         /// Registers a <see cref="AvaloniaProperty"/>.
         /// </summary>

+ 123 - 68
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
         /// </summary>
         internal IReadOnlyCollection<AvaloniaProperty> Properties => _properties.Values;
 
+        private object _unregisteringLocker = new object();
+        /// <summary>
+        /// Unregister all<see cref="AvaloniaProperty"/>s registered on types
+        /// </summary>
+        /// <param name="types"></param>
+        /// <exception cref="ArgumentNullException"></exception>
+        public bool UnregisterByModule(IEnumerable<Type> 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<Type, List<AvaloniaProperty>> dictionary,Type type)
+        {
+            dictionary.Remove(type);
+        }
+        private void Unregister( Dictionary<Type, Dictionary<int, AvaloniaProperty>> dictionary,Type type)
+        {
+            foreach (var keyValuePair in dictionary)
+            {
+                foreach (var key in keyValuePair.Value)
+                {
+                    key.Value.Unregister(type);
+                }
+            }
+        }
         /// <summary>
         /// Gets all non-attached <see cref="AvaloniaProperty"/>s registered on a type.
         /// </summary>
@@ -47,7 +97,6 @@ namespace Avalonia
         public IReadOnlyList<AvaloniaProperty> 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<AvaloniaProperty>();
-
-            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<AvaloniaProperty> 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<AvaloniaProperty>();
-
-            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<AvaloniaProperty> 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<AvaloniaProperty>();
-
-            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<AvaloniaProperty> 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<T> 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()}");
         }
 
         /// <summary>
@@ -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<int, AvaloniaProperty>();
-                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<int, AvaloniaProperty>();
                     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<int, AvaloniaProperty>();
+                        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();
         }
 
         /// <summary>
@@ -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<int, AvaloniaProperty>();
-                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<int, AvaloniaProperty>();
+                    inner.Add(property.Id, property);
+                    _attached.Add(type, inner);
+                }
+                else
+                {
+                    inner.Add(property.Id, property);
+                }
             
-            _attachedCache.Clear();
-            _inheritedCache.Clear();
+                _attachedCache.Clear();
+                _inheritedCache.Clear();
+            }
+           
         }
     }
 }

+ 6 - 0
src/Avalonia.Base/Platform/AssetLoader.cs

@@ -33,6 +33,12 @@ public static class AssetLoader
     /// <inheritdoc cref="IAssetLoader.GetAssets"/>
     public static IEnumerable<Uri> GetAssets(Uri uri, Uri? baseUri)
         => GetAssetLoader().GetAssets(uri, baseUri);
+    
+    /// <inheritdoc cref="IAssetLoader.InvalidateAssemblyCache()"/>
+    public static void InvalidateAssemblyCache(string name) => GetAssetLoader().InvalidateAssemblyCache(name);
+    /// <inheritdoc cref="IAssetLoader.InvalidateAssemblyCache(string)"/>
+    public static void InvalidateAssemblyCache() => GetAssetLoader().InvalidateAssemblyCache();
+    
 #endif
 
     internal static void RegisterResUriParsers()

+ 10 - 0
src/Avalonia.Base/Platform/IAssetLoader.cs

@@ -77,5 +77,15 @@ namespace Avalonia.Platform
         /// <param name="baseUri">The base URI.</param>
         /// <returns>All matching assets as a tuple of the absolute path to the asset and the assembly containing the asset</returns>
         IEnumerable<Uri> GetAssets(Uri uri, Uri? baseUri);
+
+        /// <summary>
+        /// Removes the assembly from the cache.
+        /// </summary>
+        /// <param name="name">The Assemblies.First().GetName().Name</param>
+        void InvalidateAssemblyCache(string name);
+        /// <summary>
+        /// Removes all assemblies from the cache.
+        /// </summary>
+        void InvalidateAssemblyCache();
     }
 }

+ 12 - 0
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();
+    }
 }

+ 10 - 0
src/Avalonia.Base/Platform/StandardAssetLoader.cs

@@ -155,6 +155,16 @@ public class StandardAssetLoader : IAssetLoader
         return Enumerable.Empty<Uri>();
     }
 
+    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)

+ 1 - 1
src/Avalonia.Controls/TopLevel.cs

@@ -312,7 +312,6 @@ namespace Avalonia.Controls
                 }
             });
         }
-
         /// <summary>
         /// Fired when the window is opened.
         /// </summary>
@@ -714,6 +713,7 @@ namespace Avalonia.Controls
             OnClosed(EventArgs.Empty);
 
             LayoutManager.Dispose();
+            _platformImplBindings.Clear();
         }
 
         /// <summary>

+ 10 - 0
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();