Working on one PoC where i have load and unload assembly on the fly and do Hot reload. i have created custom load context and loading assembly from one folder. Assembly contains only one simple class and implements interface.
public class MyPlugin : IMyPluginInterface
{
public void DoSomething()
{
Console.Write("Hello From Plugin");
}
public void DoSomethingElse()
{
Console.Write("Hello From DoSomethingElse Function");
}
}
My host project, ConfigureServices method where i am loading this assembly.
public void ConfigureServices(IServiceCollection services) { PluginDiscoveryService pluginDiscoverService = new PluginDiscoveryService();
var binFolderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
string pluginPath = Path.Combine(binFolderPath, "Plugin");
pluginPath = pluginPath + "\\MyPlugin.dll";
WeakReference hostAlcWeakRef;
IEnumerable<Type> types;
pluginDiscoverService.LoadPluginAssemblyUsingDI(out hostAlcWeakRef, out types);
foreach (var type in types)
{
services.AddTransient(typeof(IMyPluginInterface), type);
}
types = null;
var removePluginInterface = services.FirstOrDefault(descriptor => descriptor.ServiceType == typeof(IMyPluginInterface));
services.Remove(removePluginInterface);
removePluginInterface = null;
for (int i = 0; hostAlcWeakRef.IsAlive && (i < 10); i++)
{
GC.Collect();
GC.WaitForPendingFinalizers();
}
Console.WriteLine($"Unload success: {!hostAlcWeakRef.IsAlive}");
}
here hostAlcWeakRef.IsAlive is coming true. if i comment out foreach (var type in types) dont register type then i am able to unload it.
Please help
The unloading of assemblies loaded via custom AssemblyLoadContext (ALC) can indeed be difficult owing to the complex rules of .NET's garbage collector and the manner in which assemblies are loaded into memory.
In your case, it seems the problem emerges from the utilization of the ServiceCollection and the way you are registering your services. Once you register your services and the dependency injection container is built, a reference to your loaded assembly is being retained, thus blocking the ALC from being unloaded.
The first thing you need to confirm is that your PluginDiscoveryService is correctly implementing the Unload method, and you're not storing any references to the Assembly objects themselves.
If you're solely using the types and not storing any Assembly objects, then the problem could be that the DI container (IServiceCollection) is holding references to the loaded assembly. If you are in control of when these types are needed, you could ponder using factories that create these instances when necessary, and discard them instantly following use.
Then you can register MyPluginFactory instead of registering IMyPluginInterface. In this way, you govern the lifecycle of the IMyPluginInterface instances and can guarantee they get collected by the GC, permitting your ALC to be unloaded.
Furthermore, it's important to note that just because an ALC is not unloaded immediately after you call Unload() and set up your GC to collect, doesn't mean it won't be unloaded at all. The GC works in the background and will collect when it deems necessary.
If you're relying on the immediate collection of these objects for your hot reload functionality, you may need to rethink your design or compel a GC collection with GC.Collect(), GC.WaitForPendingFinalizers(), but it's generally considered bad practice to force a collection. Be sure to exercise caution to avoid strong references from a "rooted" object graph. If your loaded assemblies are still being referenced, they will not be collected and thus not unloaded.