This question already has answers here:
How to unload an assembly from the primary AppDomain?
(8 answers)
Closed 8 years ago.
I have a class which has public Assembly LoadedAssembly { set; get; }
var assemblyName = AssemblyName.GetAssemblyName(fullPathToDLLFile);
myClass.LoadedAssembly = Assembly.Load(assemblyName);
This class I keep under Application.Current.Properties["MyClass"].
I try to null it like Application.Current.Properties["MyClass"] = null; but it doesn't help.
I see that the loaded assembly works (It has a timer inside and it is working).
How do I can clean it?
(I don't use something like AppDomain d = AppDomain.CreateDomain("NewDomain", null, domaininfo); to load assembly.)
You can't arbitrarily unload an assembly, you must instead unload the AppDomain in which the assembly is loaded (which unloads all assemblies in the AppDomain). If you're not loading it into a separate AppDomain, then the only way to do that would be to shut down the application.
There are various links in this question with details on why this is not supported: How to unload an assembly from the primary AppDomain?
You cannot unload an assembly, but you can unload the AppDomain that you loaded the assembly into.
See this explanation.
Related
Problem
CSharpCodeProvider can be used to compile source .cs files into an assembly.
However, the assembly is automatically loaded into the AppDomain.CurrentDomain by default. In my case, this is a problem because I need to be able to re-compile the assembly again during runtime, and since it's already loaded in the CurrentDomain, I can't unload that, so I'm stuck.
I have looked through the docs and there seems to be no way to set the target app domain. I have also tried searching it on Google and only found answers where Assembly.Load was used, which I don't think I can use because I need to compile from raw source code, not a .dll
How would one go about doing this? Are there any alternatives or workarounds?
Main program
using (var provider = new CSharpCodeProvider())
{
param.OutputAssembly = "myCompiledMod"
var classFileNames = new DirectoryInfo("C:/sourceCode").GetFiles("*.cs", SearchOption.AllDirectories).Select(fi => fi.FullName).ToArray();
CompilerResults result = provider.CompileAssemblyFromFile(param, classFileNames);
Assembly newAssembly = result.CompiledAssembly // The assembly is already in AppDomain.CurrentDomain!
// If you try compile again, you'll get an error; that class Test already exists
}
C:/sourceCode/test.cs
public class Test {}
What I tried already
I already tried creating a new AppDomain and loading it in there. What happens is the assembly ends up being loaded in both domains.
// <snip>compile code</snip>
Evidence ev = AppDomain.CurrentDomain.Evidence;
AppDomain domain = AppDomain.CreateDomain("NewDomain", ev);
domain.Load(newAssembly);
The answer was to use CSharpCodeProvider().CreateCompiler() instead of just CSharpCodeProvider, and to set param.GenerateInMemory to false. Now I'm able to see line numbers and no visible assembly .dll files are being created, and especially not being locked. This allows for keeping an assembly in memory and reloading it when needed.
I need to check the time amount to run GetTypes() after loading the dll.
The code is as follows.
Assembly assem = Assembly.LoadFrom(file);
sw = Stopwatch.StartNew();
var types1 = assem.GetTypes();
sw.Stop();
double time1 = sw.Elapsed.TotalMilliseconds;
I'd like to unload and reload the dll to check the time to spend in running GetTypes() again.
How can I unload it? assem = null is good enough?
Is there an explicit way to call garbage collector to reclaim the resource allocated to assem?
Can you use another AppDomain?
AppDomain dom = AppDomain.CreateDomain("some");
AssemblyName assemblyName = new AssemblyName();
assemblyName.CodeBase = pathToAssembly;
Assembly assembly = dom.Load(assemblyName);
Type [] types = assembly.GetTypes();
AppDomain.Unload(dom);
Instead of using LoadFrom() or LoadFile() you can use Load with File.ReadAllBytes(). With this it does not use the assembly file but will read it and use read data.
Your code will then look like
Assembly assem = Assembly.Load(File.ReadAllBytes(filePath));
sw = Stopwatch.StartNew();
var types1 = assem.GetTypes();
sw.Stop();
double time1 = sw.Elapsed.TotalMilliseconds;
From here We cannot unload the file unless all the domains contained by it are unloaded.
Hope this helps.:)
Unfortunately you can not unload an assembly once it is loaded. But you can unload an AppDomain. What you can do is to create a new AppDomain (AppDomain.CreateDomain(...) ), load the assembly into this appdomain to work with it, and then unload the AppDomain when needed. When unloading the AppDomain, all assemblies that have been loaded will be unloaded. (See reference)
To call the garbage collector, you can use
GC.Collect(); // collects all unused memory
GC.WaitForPendingFinalizers(); // wait until GC has finished its work
GC.Collect();
GC calls the finalizers in a background thread, that's why you have to wait and call Collect() again to make sure you deleted everything.
You can't unload assembly from the current AppDomain. But you can create new AppDomain, load assemblies into it, execute some code inside new AppDomain and then unload it. Check the following link: MSDN
If you only want to load the initial assembly without any of its dependent assemblies, you can use Assembly.LoadFile in an AppDomain, and then unload the AppDomain when done.
Create a loader class to load and work with the assembly:
class Loader : MarshalByRefObject
{
public void Load(string file)
{
var assembly = Assembly.LoadFile(file);
// Do stuff with the assembly.
}
}
Run the loader in a separate app domain like this:
var domain = AppDomain.CreateDomain(nameof(Loader), AppDomain.CurrentDomain.Evidence, new AppDomainSetup { ApplicationBase = Path.GetDirectoryName(typeof(Loader).Assembly.Location) });
try {
var loader = (Loader)domain.CreateInstanceAndUnwrap(typeof(Loader).Assembly.FullName, typeof(Loader).FullName);
loader.Load(myFile);
} finally {
AppDomain.Unload(domain);
}
Assembly cannot be unloaded unfortunately, and moreover - if you use appdomains - then it will prevent you to communicate with api's / assemblies of your main application.
Best description on problem can be found here:
Script Hosting Guideline
http://www.csscript.net/help/Script_hosting_guideline_.html
If you want to run C# code without communication to your main application - then best approach is to integrate C# scripting API:
https://github.com/oleg-shilo/cs-script/tree/master/Source/deployment/samples/Hosting/Legacy%20Samples/CodeDOM/Modifying%20script%20without%20restart
And for integration you will need following packages:
C# script:
http://www.csscript.net/CurrentRelease.html
Visual studio extension:
https://marketplace.visualstudio.com/items?itemName=oleg-shilo.cs-script
If however you want to communicate from your C# script to your application - then using same appDomain with assembly name constantly changing is only way at the moment - but that unfortunately eats ram and disk space.
Code sample how to do it can be done - can be found from here:
https://github.com/tapika/cppscriptcore
CsScriptHotReload.sln
And here is demo video:
https://drive.google.com/open?id=1jOECJj0_UPNdllwF4GWb5OMybWPc0PUV
for an auto update logic, I'd like to load a specific version of an assembly. I'm trying to use Assembly.Load method with either assemlyName string or AssemblyName class parameter. For example:
string aname = "MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
var asm = Assembly.Load(aname);
This code happily loads v1.0.0.0 of MyAssembly, which I've referenced normally.
The challenge here is to load a specific version of MyAssembly. My strategy for this has been to wire up AssemblyResolve event (making sure that assembly loading doesn't happen in main method since I've read that it doesn't work there). Then I'd be debugging thru and manually changing the version number of the string from "1.0.0.0" to "2.0.0.0", expecting AssemblyResolve to fire. Surprisingly the CLR happily loads version 1.0.0.0 and fires no events.
Does anyone have a simple and working way to load a specific version of an assembly at runtime?
EDIT:
Thanks for all the answers so far. I haven't gotten it working yet the way I'd like, so your help is still needed. What I did get working was the AssemlyResolve event, like this:
int loadedAssemblies = getAsmCount(); // = 18
// LOAD V1.1 *UNREFERENCED*
var asm1_1 = Assembly.Load("_MyAssembly"); // AssemblyResolve fires behind the scene...
loadedAssemblies = getAsmCount(); // = 19
// USE V1.0 *REFERENCED IN VS SOLUTION*
// Note: seems that this Type's assembly has already been loaded!
var asm1_0 = new Class1().GetType().Assembly;
loadedAssemblies = getAsmCount(); // = 19
It seems that I have 18 assemblies loaded coming this piece of code, and to my surprise Class1 Type's Assembly (called MyAssembly, version 1.0.0.0) has already been loaded. That Assembly is referenced in visual studio normally.
When I manually load v1.1.0.0 of the same assembly, I need to use a little trick, a misspelled name, with underscore to get the AssemblyResolve event firing. Then it loads and I have 19 assemblies loaded, MyAssembly two times, once for v1.0.0.0 and once for 1.1.0.0. All fine except using 1.1.0.0. is a pain, I need to use reflection for that.
What I'd like to have to have direct access to v1.1 (the manually loaded one) of MyAssembly with this command:
var class1 = new Class1();
But now CLR gives me v1.0, the one referenced in visual studio.
How to fix this?
EDIT 2:
This question ended up morping too much, I made a compacter question here: New instance from a manually loaded assembly
you can load a specific assembly from a specific directory using:
Assembly.LoadFrom(string path)
and learn more about it here
now, you said it's your assembly, then if you decided to use reflection you can put it in a specific place (using to post-build for example to move it there) and in that way you don't have to change your code much. you can also investigate the location in order to know what files are there and load them and no loading writing the whole path hard-coded
Directory.GetFiles(string folderPath)
and then
foreach(string curr in filePaths)
{
Assembly.LoadFrom(curr )
}
You should check that there is no assembly already loaded with the same name as the one you want to load. That will prevent the loading of the second assembly.
If that isn't the case, you could use AssemblyResolve to get the assembly when .NET can't resolve it itself.
Here a small sample console app:
static void Main(string[] args)
{
AppDomain.CurrentDomain.AssemblyLoad += CurrentDomain_AssemblyLoad;
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
string aname = "MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
var asm = Assembly.Load(aname);
Console.ReadKey();
}
static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
Console.WriteLine("Resolving " + args.Name);
return Assembly.LoadFrom(#"C:\path\MyAssembly.6.0.dll");
}
static void CurrentDomain_AssemblyLoad(object sender, AssemblyLoadEventArgs args)
{
Console.WriteLine("Loading " + args.LoadedAssembly.FullName);
}
When you disable the two event handlers, you will see no assembly can be loaded. If you enable it, you will see it uses the assembly you provided.
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Unloading the Assembly loaded with Assembly.LoadFrom()
I use custom AppDomain to load/unload assembly. But when assembly is unloaded I am able to see it under the AppDomain.CurrentDomain.
How it could be? Is this normal behavior or I am missing something?
Thank you for any clue!
string assemblyPath = #"C:\MyFile.dll";
var assemblyName = AssemblyName.GetAssemblyName(assemblyPath);
var ads = new AppDomainSetup
{
ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase,
DisallowCodeDownload = true
};
AppDomain newDomainName = AppDomain.CreateDomain("newDomainName", null, ads);
try
{
Assembly testLibrary = newDomainName.Load(assemblyName);
var c1 = AppDomain.CurrentDomain.GetAssemblies();
var c2 = newDomainName.GetAssemblies();
}
finally
{
AppDomain.Unload(newDomainName);
var c3 = AppDomain.CurrentDomain.GetAssemblies();
// The assembly is still visible here!!!
}
You are calling the Load() method of an AppDomain, which according to the documentation: "should be used only to load an assembly into the current application domain. This method is provided as a convenience for interoperability callers who cannot call the static Assembly.Load method. To load assemblies into other application domains, use a method such as CreateInstanceAndUnwrap."
In other words, you're loading the assembly into the primary AppDomain because you're calling Load() from the primary AppDomain (even though you're using calling it on an instance of your secondary AppDomain), and this is why it is appearing even after you unload your secondary AppDomain.
As indicated in the extract from the documentation above, you probably want to use AppDomain.CreateInstanceAndUnwrap.
You can't remove a loaded assembly from an app domain.
http://blogs.msdn.com/b/jasonz/archive/2004/05/31/145105.aspx
http://msdn.microsoft.com/en-us/library/ms173101(v=vs.80).aspx
There is no way to unload an individual assembly without unloading all
of the application domains that contain it. Even if the assembly goes
out of scope, the actual assembly file will remain loaded until all
application domains that contain it are unloaded.
http://blogs.msdn.com/b/suzcook/archive/2003/07/08/unloading-an-assembly.aspx
There's no way to unload an individual assembly without unloading all
of the appdomains containing it.
I need to check the time amount to run GetTypes() after loading the dll.
The code is as follows.
Assembly assem = Assembly.LoadFrom(file);
sw = Stopwatch.StartNew();
var types1 = assem.GetTypes();
sw.Stop();
double time1 = sw.Elapsed.TotalMilliseconds;
I'd like to unload and reload the dll to check the time to spend in running GetTypes() again.
How can I unload it? assem = null is good enough?
Is there an explicit way to call garbage collector to reclaim the resource allocated to assem?
Can you use another AppDomain?
AppDomain dom = AppDomain.CreateDomain("some");
AssemblyName assemblyName = new AssemblyName();
assemblyName.CodeBase = pathToAssembly;
Assembly assembly = dom.Load(assemblyName);
Type [] types = assembly.GetTypes();
AppDomain.Unload(dom);
Instead of using LoadFrom() or LoadFile() you can use Load with File.ReadAllBytes(). With this it does not use the assembly file but will read it and use read data.
Your code will then look like
Assembly assem = Assembly.Load(File.ReadAllBytes(filePath));
sw = Stopwatch.StartNew();
var types1 = assem.GetTypes();
sw.Stop();
double time1 = sw.Elapsed.TotalMilliseconds;
From here We cannot unload the file unless all the domains contained by it are unloaded.
Hope this helps.:)
Unfortunately you can not unload an assembly once it is loaded. But you can unload an AppDomain. What you can do is to create a new AppDomain (AppDomain.CreateDomain(...) ), load the assembly into this appdomain to work with it, and then unload the AppDomain when needed. When unloading the AppDomain, all assemblies that have been loaded will be unloaded. (See reference)
To call the garbage collector, you can use
GC.Collect(); // collects all unused memory
GC.WaitForPendingFinalizers(); // wait until GC has finished its work
GC.Collect();
GC calls the finalizers in a background thread, that's why you have to wait and call Collect() again to make sure you deleted everything.
You can't unload assembly from the current AppDomain. But you can create new AppDomain, load assemblies into it, execute some code inside new AppDomain and then unload it. Check the following link: MSDN
If you only want to load the initial assembly without any of its dependent assemblies, you can use Assembly.LoadFile in an AppDomain, and then unload the AppDomain when done.
Create a loader class to load and work with the assembly:
class Loader : MarshalByRefObject
{
public void Load(string file)
{
var assembly = Assembly.LoadFile(file);
// Do stuff with the assembly.
}
}
Run the loader in a separate app domain like this:
var domain = AppDomain.CreateDomain(nameof(Loader), AppDomain.CurrentDomain.Evidence, new AppDomainSetup { ApplicationBase = Path.GetDirectoryName(typeof(Loader).Assembly.Location) });
try {
var loader = (Loader)domain.CreateInstanceAndUnwrap(typeof(Loader).Assembly.FullName, typeof(Loader).FullName);
loader.Load(myFile);
} finally {
AppDomain.Unload(domain);
}
Assembly cannot be unloaded unfortunately, and moreover - if you use appdomains - then it will prevent you to communicate with api's / assemblies of your main application.
Best description on problem can be found here:
Script Hosting Guideline
http://www.csscript.net/help/Script_hosting_guideline_.html
If you want to run C# code without communication to your main application - then best approach is to integrate C# scripting API:
https://github.com/oleg-shilo/cs-script/tree/master/Source/deployment/samples/Hosting/Legacy%20Samples/CodeDOM/Modifying%20script%20without%20restart
And for integration you will need following packages:
C# script:
http://www.csscript.net/CurrentRelease.html
Visual studio extension:
https://marketplace.visualstudio.com/items?itemName=oleg-shilo.cs-script
If however you want to communicate from your C# script to your application - then using same appDomain with assembly name constantly changing is only way at the moment - but that unfortunately eats ram and disk space.
Code sample how to do it can be done - can be found from here:
https://github.com/tapika/cppscriptcore
CsScriptHotReload.sln
And here is demo video:
https://drive.google.com/open?id=1jOECJj0_UPNdllwF4GWb5OMybWPc0PUV