Lonli-Lokli
Lonli-Lokli

Reputation: 3766

Are MEF exports cached or discovering every time on request?

If I have one type MyClass, register with

[Export(typeof(Myclass))] attribute, and

[PartCreationPolicy(CreationPolicy.Shared)]

or

[PartCreationPolicy(CreationPolicy.NonShared)]

and later trying to call

compositionContainer.GetExportedValue<Myclass>() multiple times.

Question: with the first call, I will get my registered class via MEF - llokup all registered assemblies, then trying to find one registered contract. Question is about second time and so on - will MEF do global lookup again or it caches somewhere internally?

Upvotes: 6

Views: 2559

Answers (3)

Daniel Rose
Daniel Rose

Reputation: 17648

Although the values/metadata might be partially cached, doing some performance testing shows that some lookup is performed every time a call to GetExportedValue is made. So if you have many calls where you need to get the value, you should do the caching yourself.

namespace MEFCachingTest
{
    using System;
    using System.ComponentModel.Composition;
    using System.ComponentModel.Composition.Hosting;
    using System.ComponentModel.Composition.Primitives;
    using System.Diagnostics;
    using System.Reflection;

    public static class Program
    {
        public static CompositionContainer Container { get; set; }
        public static ComposablePartCatalog Catalog { get; set; }

        public static ExportedClass NonCachedClass
        {
            get
            {
                return Container.GetExportedValue<ExportedClass>();
            }
        }

        private static ExportedClass cachedClass;
        public static ExportedClass CachedClass
        {
            get
            {
                return cachedClass ?? (cachedClass = Container.GetExportedValue<ExportedClass>());
            }
        }

        public static void Main()
        {
            Catalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
            Container = new CompositionContainer(Catalog);

            const int Runs = 1000000;
            var stopwatch = new Stopwatch();

            // Non-Cached.
            stopwatch.Start();
            for (int i = 0; i < Runs; i++)
            {
                var ncc = NonCachedClass;
            }

            stopwatch.Stop();
            Console.WriteLine("Non-Cached: Time: {0}", stopwatch.Elapsed);

            // Cached.
            stopwatch.Restart();
            for (int i = 0; i < Runs; i++)
            {
                var cc = CachedClass;
            }

            stopwatch.Stop();
            Console.WriteLine("    Cached: Time: {0}", stopwatch.Elapsed);
        }
    }

    [Export]
    [PartCreationPolicy(CreationPolicy.Shared)]
    public class ExportedClass
    {
    }
}

For more variations, look at this gist: https://gist.github.com/DanielRose/d79f0da2ef61591176ce

On my computer, Windows 7 x64, .NET 4.5.2:

Non-Cached: Time: 00:00:02.1217811
    Cached: Time: 00:00:00.0063479

Using MEF 2 from NuGet:

Non-Cached: Time: 00:00:00.2037812
    Cached: Time: 00:00:00.0023358

In the actual application where I work, this made the application 6x slower.

Upvotes: 0

Dennis
Dennis

Reputation: 37770

will MEF do global lookup again or it caches somewhere internally

Yes, MEF perfoms some caching and widely uses lazy initialization, if you question is about MEF performance:

1) metadata (composable parts, export definitions and import definitions) is cached. Example:

public override IEnumerable<ExportDefinition> ExportDefinitions
{
    get
    {
        if (this._exports == null)
        {
            ExportDefinition[] exports = this._creationInfo.GetExports().ToArray<ExportDefinition>();
            lock (this._lock)
            {
                if (this._exports == null)
                {
                    this._exports = exports;
                }
            }
        }
        return this._exports;
    }
}

2) exported values are cached too:

public object Value
{
    get
    {
        if (this._exportedValue == Export._EmptyValue)
        {
            object exportedValueCore = this.GetExportedValueCore();
            Interlocked.CompareExchange(ref this._exportedValue, exportedValueCore, Export._EmptyValue);
        }
        return this._exportedValue;
    }
}

Of course, when using CreationPolicy.NonShared, exported value becomes created again and again, when you requesting it. But even in this case "global lookup" isn't performed, because metadata is cached anyway.

Upvotes: 7

Jacco
Jacco

Reputation: 3272

It does a lookup every time, when you use [PartCreationPolicy(CreationPolicy.NonShared)]. You then have to implement the caching yourself.

The default implementation is using a Singleton pattern. This equals the attribute [PartCreationPolicy(CreationPolicy.Shared)]. This is the best practice.

For more information, read http://blogs.microsoft.co.il/blogs/bnaya/archive/2010/01/09/mef-for-beginner-part-creation-policy-part-6.aspx

Upvotes: 0

Related Questions