Unloading the Assembly loaded with Assembly.LoadFrom()
Asked Answered
B

6

62

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?
Barcus answered 6/6, 2011 at 21:21 Comment(3)
Short answer is that you cannot unload Assemblies; you can only unload AppDomains. There are a number of limitations imposed when you load an Assembly into a new AppDomain, so I recommend doing some research on the topic to get a feel for how they work. That said, you can load the same Assembly again, even if the prior one was not unloaded.Huron
Did someone tried what happens to the assemblies if you null them ? Do they get garbage collected or freed in any way ? I'm concerned about the memory.Anubis
A very related post here.Trapezius
E
40

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.

Eiderdown answered 6/6, 2011 at 21:26 Comment(2)
I'm loading (and potentially unloading) dozens of dlls. What are the performance implications (and other relevant implications) of creating, say, 70 AppDomains? Should I avoid doing that if possible?Verlie
I suppose if it's just for this operation that would work, but in a situation where I actually want my application to instantiate some objects from the assembly, use them, destroy them and unload the assembly, wouldn't loading it in a separate AppDomain cause some accessibility headache?Kyrstin
E
72

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);
Emmuela answered 6/6, 2011 at 23:29 Comment(5)
Fails for me : it throws at dom.Load a "FileNotFoundException". The funny thing is that the file really exists, and the exception message contains the exact version of the file (which is not 1.0.0.0, so i'm sure it eventually found my file...).Chappie
In my case the "FileNotFoundException" was caused by one of the referenced assemblies. I ended up using Assembly.ReflectionOnlyLoadFrom in conjuction with a method that loads all the referenced assemblies into the reflection only context.Bearded
for some reason this loads the assembly with the same name (as mentioned) present in the application directory, rather than the assembly in the directory mentioned...any idea why this might happen ??Indamine
Unfortunately this does not work for .NET Core as AppDomains are not implemented.Demarche
System.PlatformNotSupportedException: 'Secondary AppDomains are not supported on this platform.'Hectogram
G
57

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.:)

Gipsy answered 17/1, 2017 at 12:32 Comment(10)
You can't pass an byte array to Assembly.LoadFrom(). I edited your post, but somebody changed it back.Loo
@Loo Actually, you can, and it works very well. I load dlls as plugins dynamically and then when its the moment of uninstall them I should erase the file. The Hardik method works perfectly, no AppDomain hassles or something. roli09 You should test the code before doing any modifications to comments, this should have been the right answer,Kus
@Kus It works because Hardik is using Assembly.Load and not Assembly.LoadFrom anymore.Loo
Does this leak memory when assem is set to null?Sanderling
@Gipsy is correct: The Assembly Load method holds a reference to the file loaded, preventing that file from being overwritten or deleted by another process. So it's a clever workaround to use the File object's ReadAllBytes method, which does not hold a reference. Once those bytes are read and the file is closed, the result is passed to the Assembly Load method. Quite clever!Serapis
This is now the best solution although it currently has less than half of the points the leading answer.Coady
@Gipsy Thank you! Thank you so much! It saved my day!Augend
But this wont work when your assembly has dependencies. We will get LoaderExceptions. What to do in this case?Cerumen
@Cerumen everything has dependencies when you consider namespaces like System. i haven't encountered this. maybe because i'm compiling a single fsx file (the only "dependencies" are namespaces at the top of the file). i'm following this in F# (jamesdrandall.com/posts/…). and compiling with let compilerArgs = [|"-a";"script.fsx";"--targetprofile:mscorlib";"--target:library";"-o";"script.dll"|]. basically the dependencies have already been referenced, by the application loading the assembly (my dependencies are don't change)Elias
@Cerumen everything has dependencies like System. i haven't encountered this. maybe because i'm compiling a single fsx file (the only "dependencies" are namespaces at the top of the file). i'm following this in F# (jamesdrandall.com/posts/…). and compiling with let compilerArgs = [|"-a";"script.fsx";"--targetprofile:mscorlib";"--target:library";"-o";"script.dll"|]. basically the dependencies have already been referenced, by the application compiling and loading the assembly (my dependencies don't change)Elias
E
40

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.

Eiderdown answered 6/6, 2011 at 21:26 Comment(2)
I'm loading (and potentially unloading) dozens of dlls. What are the performance implications (and other relevant implications) of creating, say, 70 AppDomains? Should I avoid doing that if possible?Verlie
I suppose if it's just for this operation that would work, but in a situation where I actually want my application to instantiate some objects from the assembly, use them, destroy them and unload the assembly, wouldn't loading it in a separate AppDomain cause some accessibility headache?Kyrstin
V
5

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);
}
Villareal answered 12/6, 2019 at 1:46 Comment(0)
C
4

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

Clepsydra answered 6/6, 2011 at 21:26 Comment(0)
J
0

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

Jann answered 11/2, 2019 at 21:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.