Prevent MEF from locking files

Aug 11, 2009 at 8:13 PM
Edited Aug 12, 2009 at 4:04 PM

I was able to prevent MEF from locking files, and thought other devs may want to as well.

This would allow MEF extensions to be edited, rebuilt, etc. during runtime of the app. As long as no composition occurs while an extension is being edited,

the previous version will stay in memory until recomposition occurs, at which time the new version will be loaded.

Change the following code in AssemblyCatalog.cs from:

 

private static Assembly LoadAssembly(string codeBase)
        {
            Requires.NotNullOrEmpty(codeBase, "codeBase");

            AssemblyName assemblyName;

            try
            {
                assemblyName = AssemblyName.GetAssemblyName(codeBase);
            }
            catch (ArgumentException)
            {
                assemblyName = new AssemblyName();
                assemblyName.CodeBase = codeBase;
            }

            return Assembly.Load(assemblyName);            
        }

 

to:

 

private static Assembly LoadAssembly(string codeBase)
        {
            byte[] assemStream = null;
            using (FileStream fs = new FileStream(codeBase, FileMode.Open))
            {
                assemStream = new byte[fs.Length];
                fs.Read(assemStream, 0, (int)fs.Length);
            }
            
                return Assembly.Load(assemStream);
            }
        }
The assembly  will still be in memory, but the file will not be locked and can be edited then recomposed.
Seems like a much better workaround than messing with AppDomains and ShadowCopying.
(Note: This approach has only been tested using the Directory Catalog.)
Also, to see my use of MEF with Microsoft's Visual Studio Tools for Applications (VSTA), please visit my blog: http://www.summsoft.com/blogs/vstanewbie/default.aspx
Aug 13, 2009 at 4:01 AM
Edited Aug 13, 2009 at 4:34 AM

Just be warned that any assembly loaded by bytes is placed under the 'no context' reflection context. This means that any types loaded in this context will be seen as completely different types from any of the same types loaded in the normal 'load' context. In otherwords, make sure that any assemblies you load this way are not explictly referenced by other assemblies - otherwise you are in for a world of pain. ;)

 

Aug 13, 2009 at 2:32 PM

Thanx for the tip, and for all you guys do on the project!

Aug 20, 2009 at 6:37 AM

This is interesting information, thank you! I see you mention the ShadowCopying as an alternative solution and I was wondering what are the cons of using it? Is it hard to implement or is there some other reason for avoiding shadow copying?

Aug 20, 2009 at 6:59 AM

Using ShadowCopy at the app-domain level can work, however not without caveats. Sure it does not lock files, and you can replace them. However, when you do you end up with a memory leak that grows each time you do it. For example if you put V1 of an assembly, and then you drop V2 of the assembly in and then load it, well V1 is still there hanging around and never goes away, until you reset the app. Now for a desktop app that opens and closes frequently that may be OK. In server environments, the cost might be too great.

Glenn

Aug 20, 2009 at 1:33 PM

@gblock Which approach would you more recomend? i.e. which seems to have the least number or lowest impact "cons?"

Developer
Aug 20, 2009 at 5:46 PM

In most cases if you simply want to prevent the assembly from being locked I would lean towards the ShadowCopy approach. With the ShadowCopy approach you will eliminate the loader context issues that David mentioned, which can be extremely difficult to understand and debug. In both cases you still have the memory leakage issues because even with the Assembly.Load(byte[]) solution you are never unloading the previously loaded assemblies so they still stay around in memory.

One other thing to consider in doing this is calling Assembly.Load on two assemblies with the same identity (i.e. name, version, etc) will actually not load the second assembly, even if the code in the assembly is different. Before it does the load it will see if it has already loaded this assembly in the given context and it uses the identity to determine this and if it finds a match it will actually return you the already loaded assembly instead of loading the new one. I’ve not tested this explicitly but I do believe this works the same with both Assembly.Load(AssemblyName) and Assembly.Load(byte[]). Now the saving grace about this is for most default projects the AssemblyVersion has a “*” in it somewhere in which case every time you build the version number will change and so the identities of two build assemblies will be different.

Aug 24, 2009 at 11:32 AM

I think this as good place as any for my question: It doesn't seem to be possible to change the ShadowCopy-property of the default application domain, am I correct?

Developer
Aug 24, 2009 at 10:47 PM

I'm not a loader/AppDomain expert but I don't believe you can actually set the ShadowCopy property on the AppDomain that you are currently running in because to do so would mean that there is already assemblies loaded in the AppDomain and they would not be ShadowCopied.

Check out http://jdscolam.blogspot.com/2008/01/joys-of-appdomains.html for some ideas and links to information.