Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lazy loading of assemblies/catalogs

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:

  • Being able to access plugin assemblies' export metadata without loading their entire assembly.
  • Transparent integration with the code importing the parts; i.e. use imports as usual - someone else (a specialized catalog?) will take care of loading the assemblies if necessary and provide the requested part.
  • Not losing any existing MEF functionality such as recomposition, lazy types, etc.

I'm completely fine with a solution that requires parsing the plugins in advance to create a metadata assembly, XML file or whatnot.

like image 660
Adi Lester Avatar asked Nov 03 '22 13:11

Adi Lester


1 Answers

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.

like image 172
Petrik Avatar answered Nov 15 '22 07:11

Petrik