Unloading an Assembly


There’s no way to unload an individual assembly without unloading all of the appdomains containing it. (See here for why not.) This can by done by calling AppDomain.Unload() for each AppDomain that has it loaded. (You could also use UnloadDomain() on the v1 unmanaged hosting API, but in general, I recommend using managed code whenever possible.)


I should point out that this means that even if your managed Assembly/Module/etc. instances go out of scope, GC may clean those instances up. The actual file will remain loaded until you unload all of the appdomain(s) containing it, however. (In general, that’s better, anyway. Loading a file, especially by http, is too expensive to be done over and over when it can just be cached.)


If you want to unload some assemblies but not others, consider creating a new AppDomain (requires ControlAppDomain permission), execute the code using the temporary ones from within that AppDomain, and then unload that new AppDomain when you’re done. I recommend minimizing the number of AppDomains, though, since there is overhead involved in creating them, transitioning between them, and unloading them. So, it’s best if you do all of that work at once, in one AppDomain, minimizing the cross-domain communication, and just unload that when possible (as opposed to creating a new AppDomain for each assembly to unload, etc.).


Gotchas
Watch out for references to that assembly leaking to other appdomains. If that happens, that assembly will remain loaded if you just unload one AppDomain. For example, passing a Type object to another appdomain will cause its assembly to be loaded there.


Calling Unwrap()/AppDomain.CreateInstanceAndUnwrap()/etc. may also cause the runtime to try to load the assembly in the calling appdomain. In that case, call CreateInstance*() on a MarshalByRefObject type from a different assembly. For example, using AppDomain.CreateInstanceFrom() on the calling assembly (the one currently calling Unwrap()) may be the most convenient way. Then, call a method on it which will do all of your assembly loading. That way, those assemblies can be loaded in just the target appdomain.


Also, note that assemblies loaded as domain neutral will not be unloaded until the process exits, since other appdomains are technically still using them.


Alternatives
But, maybe you don’t need to load that Assembly, anyway. If you just need info about it, you could call AssemblyName.GetAssemblyName() with the path to the file. That will temporarily load the file to get the info and then immediately unload it. That call will not cause it to show up as an Assembly in the AppDomain.


Or, if you’re only worried about keeping the file locked, you could use shadow copying. That will make a copy of the file on disk and load it from the new location. The original file will not be locked by that load.  To do that, set AppDomainSetup.ShadowCopyFiles to “true” when creating the AppDomain or set AppDomain.ShadowCopyFiles to true after it’s already been created.

Comments (30)

  1. Marek says:

    Hello,
    As you mentioned file shadowing can keep the originals unlocked, however what if we need the process entry assembly unlocked? How does one enable shadowing on the default application domain; so that the entry assembly is shadowed not just subsequent assembly loads? The app config file doesn’t seem to have any settings related to this. It seems it should be possible. Thank You

  2. suzanne, How more a read how less a understand .
    Can you give a explantion about AppDomain’s.
    I have made my own Mpp.Wsis.Mod_sys.ddl with Namespace Mpp.Wsis.Mod_sys
    in the dll is a class named public class Task………
    i put the dll in the gac but when i make a sentence like these in asp.net :
    dim a = new Mpp.Wsis.Mod_sys.Task()
    there is a error, need a AppDomain’s to use the class?

    thanks for your time

  3. Phil says:

    Can you somehow specify files in addition to the assembly to be shadow copied as well? What if you have a config file that needs to be in the same directory as the shadow copied assembly, how would you get the config file copied to the shadow directory that the assembly was copied to?

  4. Suzanne says:

    Marek: To enable shadow copying on the default AppDomain, you can set AppDomain.ShadowCopyFiles to true on it. But, that won’t help for the process exe, since it’s loaded directly by the OS, not us. (Since the OS is the one holding the lock, it being shadow copied and then reloaded from there will just mean that there are two locked copies.) If you need the original exe to not be locked, you’ll have to make a copy and call that instead.

    Michael: I’d need to know what the exact error message was. However, probably the answer you want is at http://blogs.msdn.com/suzcook/archive/2003/05/29/57120.aspx . ASP.NET already creates an AppDomain for you, so I imagine that you won’t need to create another one for your purposes.

    Phil: I’ve already answered your question at http://blogs.msdn.com/suzcook/archive/2003/06/26/57198.aspx#comments .

  5. Roy says:

    I ran into a locking problem lodading assemblies for an AddIn, and creating a seperate App Domain was not only VERY difficult, but seemd like over kill. To avoid the locking of the file, I put the assembly into a byte[] and then just used the byte[] to load the assembly. This prevents the locking, and still allows the assembly to be loaded into the current app domain.

  6. Suzanne says:

    Yes, that’s an option for some cases, but too disadvantageous for others. It puts the assembly in neither the Load nor the LoadFrom context. See my blog entry at http://blogs.msdn.com/suzcook/archive/2003/05/29/57143.aspx for details about the advantages and disadvantages of that. I wonder what you mean about creating an AppDomain being difficult, though. Just creating one is as easy as calling AppDomain.CreateDomain(). Instructions for using a new AppDomain are described in http://blogs.msdn.com/suzcook/archive/2003/06/12/57169.aspx .

  7. Roy says:

    Yes, creating an App domain is simple in itself. But since this is an add-in for the IDE, I would have had to duplicate the IDE’s app domain, and no matter how hard I tried, I could never get it right.

  8. Roy says:

    BTW, for this addin, I only needed to do a bit of reflection on the assembly to get it’s type information (classes, enums, methods, ect) and output it to an XML file. I’m never actually using the assembly for anything.

  9. Suzanne says:

    I’m not sure what you mean by being unable to duplicate that AppDomain. Since all you care about is reflection, you may only care about the AppDomainSetup being same (pass in AppDomain.CurrentDomain.SetupInformation)…unless you’re depending on the order dependence of the LoadFrom context, or on the AssemblyResolve callback. I’d recommend against relying on those, anyway (see http://blogs.msdn.com/suzcook/archive/2003/05/29/57143.aspx ). But, if you still decide that loading by byte[] is better, keep in mind that reflection will load its dependencies. So, if its dependency is a file that you don’t want locked, if it’s in the Load context, it will be loaded automatically, without raising the AssemblyResolve event, even if you’ve already loaded it by byte[].

  10. RoyS says:

    >> I’m not sure what you mean by being unable to duplicate that AppDomain. Since all you care about is reflection, you may
    >> only care about the AppDomainSetup being same (pass in AppDomain.CurrentDomain.SetupInformation)…

    I tried using the CurrentDomain.SetupInformation, but still ran into problems loading the assembly (it could never be found, even when I gave it the full path in the Load/LoadFrom. One thing that I could not get correct was to modify the PrivateBinPath to include all my assembly paths. I’m going to be working in that code again this weekend, so I may give it another shot.

    >> better, keep in mind that reflection will load its dependencies. So, if its dependency is a file that you don’t want locked, if it’s
    >> in the Load context, it will be loaded automatically, without raising the AssemblyResolve event, even if you’ve already loaded
    >> it by byte[].

    Really? I was not aware of that. I’ll have to try a few tests and see what happens with my dependencies and if they get locked.
    The probelm is that under the IDE, assemblies loaded in an AddIn are loaded into the IDE’s AppDomain, and if I load a projects assembly, it gets locked, and I can never build the project again until I shut down the IDE and restart it.

  11. Suzanne,

    Thanks for a wonderful weblog.

    Why doesn’t an app’s private address space shrink if an AppDomain is unloaded? See the last paragraph of http://www.jelovic.com/weblog/e113.htm for details.

    Also, my app causes the COR to puke on application startup five or six times per day and show the "Failed to load resources from resource file/Please check your setup" message box. Looking at the Rotor sources I see that this message is displayed when COR is so confused in cannot even find the resource strings required to display a proper error message. Ping me via e-mail if you are interested in seting things up so that you can see this problem in a debugger.

    Dejan

  12. Mike Wright says:

    Is there any way to enable ShadowCopy on the current app domain? I am trying to load, using CreateInstanceAndUnWrap, an assembly that I would like shadow copied. I want to do this within the current app domain.. Thanks.

    – Mike

  13. Suzanne says:

    Roy: See http://blogs.msdn.com/suzcook/archive/2003/05/29/57120.aspx for advice about debugging assembly loading problems. Try looking at the paths that were probed for this file in the Fusion log. About the byte[] thing: this can be worked around by changing the assembly identity (changing the assembly version for each build). That will cause it to be reloaded, and will avoid the problems caused by using byte[] assemblies.

    Dejan: Thanks! I’ve forwarded your first question to our perf team, and will let you know. For the rest, that message sounds like a resource file is missing or corrupt. Did you try a reinstall? Plus, which resource is it asking for? Usually, this would come up while retrieving an error message (so there may be some other problem besides being unable to find the resource).

    Mike: Yes, set ShadowCopyFiles to true on that AppDomain instance.

  14. Suzanne says:

    Dejan: Rico, from our perf team, had this to say:

    It’s really very difficult to say what’s happened here with any kind of accuracy. Often if there is a great deal of heap usage you can trace it to live objects that are staying live much longer than they need to. The creation of an appdomain in this customer’s scenario could be causing some domain neutral assemblies to be loaded which are not then unloaded. And, of course, there’s the question of what the code itself does and how much of it lives and for how long.

    I don’t know what guidance I can give you overall other than:

    -look into the key performance counters for the GC (really logging all of them can be useful), and
    -use CLRProfiler to see what’s really being allocated and when and then use that to drive modifications .

    Really, the trouble is that it’s easy for people to inadvertantly ask for services that will require large swaths of memory. It’s very easy to then just assume it’s because .NET is fat, but at the core the CLR doesn’t do allocations on the order of magnitude that this person is seeing. He’s activated some hefty framework pieces somehow – the question is, which and how.

  15. Mike Wright says:

    Thanks for the info. My problem is that I need to dynamically load assemblies into either the current appdomain with Shadow Copyiing enabled, but when I turned it on, Shadow Copying did not occur. I read in the MSDN that you couldn’t set this after the "first binding" had occured, but I was not sure what that was. I created a new app domain and have shadow copying working on that, but when I try and load certain assemblies, I get a "Cannot find assembly" error. However, I KNOW that it is finding it, as the constructor for the class I am trying to load indeed runs. As soon as the constructor completes, the exception is raised. Any help is MUCH appreciated. FYI – what we’re doing here is running C# .NET forms as MDI children of a VB 6 project, and we almost have it working.

  16. Suzanne says:

    I should have said to call AppDomain.CurrentDomain.SetShadowCopyFiles() – ShadowCopyFiles is a getter property, with no setter. Where is that doc? It needs to be corrected – you can set it, and it will take effect, even after the first binding (the first assembly load in that appdomain). Just don’t bother setting it on the AppDomainSetup for an AppDomain after that AppDomain has already been created – it will have no effect. It sounds like a dependency of that assembly is failing to load. See http://blogs.msdn.com/suzcook/archive/2003/05/29/57120.aspx for debugging advice.

  17. Mike Wright says:

    Thanks again for the info. I’d certainly like to NOT have to create a new AppDomain, since that creates it’s own set of problems for me. I can’t find any examples of enabling ShadowCopy on the current app domain, but this is what I am doing:

    AppDomain.CurrentDomain.SetShadowCopyPath(this.m_probePath);
    AppDomain.CurrentDomain.SetShadowCopyFiles();

    this.m_probePath contains the directory of the assemblies I want to shadow copy. But, my application base is actually the directory of the VB 6 application.. All the assembly loads fail as it claims that it cannot find the assemblies. How do I make it recognize m_probePath as the source for my assemblies? Thanks again.

  18. Suzanne,

    Re the problem with COR puking:

    I don’t think this is due to a bad install. It happens on three different boxes that were set up differently by three completely differente people. Plus, I can get the problem to rear its head more often if I pause the execution of my program in the debugger at certain points. My guess is that this is a race condition somewhere inside the COR.

    Also, when this happens all that has executed up to that point is verifiable code without any P/Invoke, so we can also rule out any memory corruption by my app.

    I don’t know what resource the COR is asking for at that point. I don’t have your symbols.

    Again, you are welcome to attach a debugger when this happens.

    Re Rico’s response:

    I just crippled my unit testing code so that _all_ it does is search through a bunch of assemblies for methods with a given attribute, _without_ ever invoking those methods.

    I also made sure that all the assemblies that the new AppDomain will load are already loaded in the primary AppDomain.

    Yet I still end up with a private address space a couple of megs larger than before.

    The only thing that I can conclude is that using reflection eats up large gobs of memory which are never freed. Can you check with Rico if that’s the case?

    Thanks,
    Dejan

  19. Suzanne says:

    Mike: SetShadowCopyPath() is not like specifying an extra ApplicationBase – those assemblies will need to be find-able in the normal way. (You may want to consider creating a new AppDomain with m_probePath as the ApplicationBase, and run your code from there – see http://blogs.msdn.com/suzcook/archive/2003/06/12/57169.aspx .) SetShadowCopyPath() will just make it so that only those dirs will have assemblies shadow-copied from there, not all dirs.

    Dejan: This is getting to be specific enough to you that it’s better if I just follow up over email on both issues.

  20. Mike Wright says:

    Thanks for the clarification on SetShadowCopyPath() – the MSDN docs are not entriely clear on this. When I use another AppDomain, I run into the other problem I presented earlier, where certain assemblies do not load. I will go back and follow your advice on trying to determine why these assemblies error immediately after the contructor of the class I am creating runs. It’s very strange. Thanks for the assistance.

  21. juju says:

    I wanted to use c# to copy some of the functionallity on the Unreal Engine, where the level designer can recompile the script source for an object(actor), recompile the script, and use it again in the UnrealEd…. the problem is that I CAN’T unload my previous version of "the object" to replace it for the new one … I tryed to use a different AppDomain to load/unload "the object", but got multiple serialization/marshaling exceptions … now the exceptions are solved, but the use os multiple domains has BRUTAL overhead….

  22. Speaking of dynamic IL generation …

    Before Whidbey, the framework supplied two ways of creating code…

  23. Steve says:

    Suzanne,

    You’ve mentioned that it’s possible to call AppDomain.CurrentDomain.SetShadowCopyFiles to enable shadow copying on the current AppDomain.

    However, I notice in .Net 2.0 this method is marked as obsolete. The documentation suggests that we should be creating a new AppDomain and using AppDomainSetup.ShadowCopyFiles. Should I really be doing this in .Net 1.1 as well? Is there a real reason for SetShadowCopyFiles now being obsolete, or is it just that it’s not the preferred way of doing things?

    Thanks,

    Steve.

  24. B# .NET Blog says:

    Introduction

    The last couple of days I’ve been playing around with some stuff around WCF (formerly known…

  25. GIS pro says:

    I am developing an addin which reads when clicked retrieves modules and their field information in the project assembly. Inorder to do this I am creating an application domain and trying to load the project assembly so that I can retrieve all modules in the assembly using getloadedmodules method.

    I am having problem in loading the assembly. when I try to load the assembly it is throwing an error related either fileiopermissionaccess failed or securitypermission failed.

    I am new to .net and I am currently using .net 2003.

    Can any one suggest an alternative to this.

    Thanks.

  26. Pushkar says:

    There hasnt been much of activity on this blog lately but I will still post as is.

    Why I need Assembly.Unload ->

    I am designing a UI based plugin Manager which loads UI plugins dynamically.

    Explaination ->

    My problem starts with the basic fundamental that for any UI based activity the UI thread can be the only thread which can access any of the UI components.

    Now these components are dynamically loaded on the main form and displayed. Creating these components in an appdomain contradicts the above fundamental because appdomain implies multi-threading and marshalling. If we were able to marshal UI, that would great but I dont think that it is really supported. The UI’s components Refresh(), OnPaint … blah blah … need to be called in main UI thread.

    OK. The only alternative I suppose is to load the assembly in the same appdomain as the main UI thread. Great my UI works great. But what if the component needs to be updated. The assembly is in use, the component can not be copied over. Hmmmm. The plugin manager needs to be shutdown, assembly should be copied and appication restarted.

    Alright lets just say that I somehow have a versioning SxS logic in place  and updates for the  component is placed in a directory hierarchy => somedirNameverXXsomeassembly.dll. Now, I keep watching the directory. So finally at the cost of loading X number of assemblies SxS (side by side) I can have all of them loaded.

    However if the first version is 50M and the second is 100M, my lean mean Plugin Manager is bloated to 150M.

    Jason Zanders tried to explain that best way is to separate UI from application logic but this is purely a UI plugin manager. ex. 2 versions of button controls …

    So, I think for UI plugin managers,  Assembly.Unload is absolutely needed.

    I hope you agree. If you dont let me know the solution, I would be dying to know it.

    Sorry for any grammatical mistakes, but hopefully you get the point – it is 12:30pm Fri nite.

  27. Wilke Jansoone says:

    Hi,

    I’m dynamically loading an assembly in a new AppDomain in order to list the types it ccontains, after that the assembly is removed via the unload of the AppDomain. I use a proxy class to load and reflect the assembly. The reflection happens in a sharepoint context.

    So far the facts.

    When I recompile the assembly (after adding or removing types) that I am reflecting on and store it in the GAC the change in types is not ‘reflected’. If I remove the assembly from the GAC I receive an error the the assembly can not be found, if it finds the assemblty it is still the previous version. If I do an application pool recycle the changes are reflected. So it seems that even though the assmbly is unloaded (I can verify that it never has been loaded in the main appdomain by looking at AppDomain.CurrentDomain.GetAssemblies()) it still hangs around somewhere.

    Anyone has any idea?

    Thanks in advance,

    Wilke

  28. Mitan Shah says:

    Hi,

    I want to know is there any limit for loading assembly in one AppDomain, bcos i m planning to generate assembly runtime depends on my user's input so every time i will generate new assembly and load into current domain.

    Thanks in Advance.

Skip to main content