My application is comprised of a few core assemblies and several extension/plugin assemblies. In order for MEF to know all the parts the plugins have to offer, I have to load these assemblies even if I'm never going to use any of their parts. This makes the application take more time to start (if I'm going to load all the assemblies on startup) and also increases the memory footprint.
Ideally, I won't have to load the assemblies until I actually need them. I would load only the plugins' export data, and when I actually need to import a part, MEF would load the assembly and provide the part.
I found that there is something that does pretty much everything I just wrote, but after asking about it in MEF CachedAssemblyCatalog - Lazy Loading of Assemblies, I realized this code isn't considered stable and is not being maintained by the MEF team, so I've decided not to use it.
My question is how then can I achieve this behavior:
I'm completely fine with a solution that requires parsing the plugins in advance to create a metadata assembly, XML file or whatnot.
If you are just after delay loading the assemblies then you could potentially use part of the solutions to this question. You wouldn't need to grab all the information that is retrieved in that solution. Probably only the contract name, assembly name and whether the part is exporting or importing the contract. Then you can write a catalog that loads the assemblies as you need them, for instance like this:
public sealed class DelayLoadingCatalog : ComposablePartCatalog
{
// List containing tuples which have the 'contract name'
// and the 'assembly name'
private readonly List<Tuple<string, string>> m_Plugins
= new List<Tuple<string, string>>();
private readonly Dictionary<string, AssemblyCatalog> m_Catalogs
= new Dictionary<string, AssemblyCatalog>();
public DelayLoadingCatalog(IEnumerable<Tuple<string, string>> plugins)
{
m_Plugins.AddRange(plugins);
}
public override IEnumerable<Tuple<ComposablePartDefinition, ExportDefinition>> GetExports(ImportDefinition definition)
{
var partsToLoad = m_Plugins
.Where(t => t.Item1.Equals(definition.ContractName));
foreach (var part in partsToLoad)
{
if (!m_Catalogs.ContainsKey(part.Item2.Name))
{
var assembly = Assembly.Load(new AssemblyName(part.Item2.Name));
m_Catalogs.Add(part.Item2.Name, new AssemblyCatalog(assembly));
}
}
return m_Catalogs.SelectMany(p => p.Value.GetExports(definition));
}
public override IQueryable<ComposablePartDefinition> Parts
{
get
{
throw new NotImplementedException();
}
}
}
which you can then use like this
class Program
{
public void Init()
{
var domainSetup = new AppDomainSetup
{
ApplicationBase = Directory.GetCurrentDirectory(),
};
var scanDomain = AppDomain.CreateDomain(
"scanDomain",
null,
domainSetup);
var scanner = scanDomain.CreateInstanceAndUnwrap(
typeof(MyScanner).Assembly.FullName,
typeof(MyScanner).FullName) as MyScanner;
var plugins = scanner.Scan(myPluginsPath);
// Make sure we don't have the assemblies loaded anymore ...
AppDomain.Unload(scanDomain);
var catalog = new DelayLoadingCatalog(plugins);
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
}
[Import("MyCoolExport")]
public object MyImport
{
get;
set;
}
}
The example DelayLoadCatalog isn't very smart as it will keep searching through the list of Tuples. Optimizing the code shouldn't be too hard though. For example you could however check if all assemblies have been loaded and stop searching through that list at that point.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With