Last week, I had a problem that probably some .Net developer had faced. The problem consists in load and unload an assembly?.
Can I do that??? Well, I can’t do it in a simple way. One of the things that Assembly doesn’t support in unload, check this post to see why.
So, like I wrote, last week I was developing a new feature for a software factory and that feature consisted in get all references and do something else.
My recipe does something like this:
- Get project assembly
- Copy the assembly DLL to a location
- Get all assembly references
- Generate a file
Do number 1 is simple.
Do number 2 is also simple, but has a problem.
Do number 3 is also simple, but has a problem.
Do number 4 is simple.
I’m going to explain first problem number 3 then problem 2
The problem with number 3 it’s because I load a assembly and they I get referenced assemblies. To get referenced assemblies I just do:
Assembly assembly = Assembly.LoadFrom(@"c:\myAssembly.dll");AssemblyName[] references = assembly.GetReferencedAssemblies();
This piece of code has a problem, since I’m loading the assembly and they get referenced assemblies the file myAssembly.dll is now locked this means that the next time I try to run number 2 I will get an exception and the file will not ne copied. And this is why I had problem number 2.
Now, how can I load the assembly and then unload that assembly so the file won’t be locked???
I came across some solutions.
- One of the possible solutions is just copy the assembly to something unique like {GUID}.DLL and then load that {GUID}.DLL, and do everything I need to do, this way I won’t lock my file.
- Problem: I will have the assembly {GUID}.DLL loaded (in memory) every time, and can’t delete file {GUID}.DLL, because it’s locked. And finally I can’t unload unless I close Visual Studio.
- Why don’t I just load the assembly into an array of bytes and then load the assembly, like this:
Assembly assembly = Assembly.Load(File.ReadAllBytes(@"c:\myAssembly.dll"));
- Problem: I can do this and my file will not be locked but that array of bytes will be in memory and my visual studio memory will grow every time I run that recipe. That memory will be released only when I close the Visual Studio.
- I can load the assembly in a new AppDomain and then unload the AppDomain.
- Problem: tThis was a strange problem and I don’t know why but I load the assembly in a new AppDomain and that assembly become part of the new AppDomain and the AppDomain.Current. This was strange. Don’t know if I was doing something wrong. Now since the Assembly is loaded in AppDomain.Current I wasn’t able to copy the file, because it was locked.
- The last solution I came across to load the assembly and unload it. This solution Consists in create a new AppDomain and then use method DoCallBack:
Here it is the source code:
AppDomainSetup setup = new AppDomainSetup(); setup.ApplicationBase = ApplicationBasePath;AppDomain newDomainReferences = AppDomain.CreateDomain("AppDomain", null, setup); CallBackAssemblyReferences call = new CallBackAssemblyReferences(newDomainReferences, filepath); newDomainReferences.DoCallBack(new CrossAppDomainDelegate(call.LoadAssemblyReferences));AssemblyName[] assName = (AssemblyName[])newDomainReferences.GetData("AssemblyReferences");AppDomain.Unload(newDomainReferences);
This solution enables me to do a callback in a different domain. My class CallBackAssemblyReferences just does this:
[Serializable]internal class CallBackAssemblyReferences{#region Members Variablesprivate string assemblyFile;private AppDomain domain;#endregion #region Public Implementationpublic CallBackAssemblyReferences() { } public CallBackAssemblyReferences(AppDomain domain, string assemblyFile) {this.assemblyFile = assemblyFile;this.domain = domain; }public void LoadAssemblyReferences() {Assembly assembly = this.domain.Load(AssemblyName.GetAssemblyName(this.assemblyFile)); domain.SetData("AssemblyReferences", assembly.GetReferencedAssemblies()); }#endregion}
This class has a constructor that receives the new domain I’m working on and the path to the assembly I want to load.
public CallBackAssemblyReferences(AppDomain domain, string assemblyFile)
Then I just have my callback method WITH NO PARAMETERS.
public void LoadAssemblyReferences()
This is the method that will be called when I do
newDomainReferences.DoCallBack(new CrossAppDomainDelegate(call.LoadAssemblyReferences));
The callback method only loads the assembly in the new domain and then sets data in the new domain. These data are the assembly references.
When the DoCallBack returns I just had to do
AssemblyName[] assName = (AssemblyName[])newDomainReferences.GetData("AssemblyReferences");
To get the references I had saved.
Finally I just unload the AppDomain.
Now I can run my recipe every time I need without lock the file and without decrease visual studio performance.