MEF Preview 5, changes and enhancements

We recently shipped MEF preview 5 on Codeplex, an exciting release.

In the latest release, you’ll find we’ve made quite a few changes, and some really powerful enhancements to our previous codebase. In this post, I’ll talk about those changes in detail, and why we made them. I’ll also discuss some changes we will be making in the future, in order to give you a heads up, so that you can adjust your code bases now.

Throughout the development of preview 5, we’ve done our best to minimize the impact to you. There are some breaking changes, primarily in terms of namespace refactoring, most of which do not effect part authors, but will effect MEF hosters, and authors of custom programming models or export providers.

Here’s a summary of these changes for those who want to just cut to the chase. For those who want the full skinny, that comes after the summary.

Change

Summary description

New sample application, MEF Studio
  • We have included a new sample application which is a mock pluggable IDE using MEF.

Namespace changes

  • System.ComponentModel.Composition – For part authors
  • System.ComponentModel.Composition.Hosting – For hosters
  • System.ComponentModel.Composition.Primitives – Infrastructure for defining programming models
  • System.ComponentModel.Composition.ReflectionModel – Services for implementers of catalog caches, and for reflection based programming models.

Part Discovery Model and Lifetime Changes

  • CompositionOptions has been removed
  • New PartCreationPolicyAttribute  for specifying part lifetime (Replaces CompositionOptionsAttribute.CreationPolicy )
  • Exports are not discovered on base classes by default
  • New PartNotDiscoverable attribute for specifying that a part should not be discovered by the catalog. (replaces [CompositionOptions(DiscoveryMode = DiscoveryMode.Never)])
  • New PartExportsInherited attribute allows a base class / interface to declare itself as discoverable by inheritors. (replaces [CompositionOptions(DiscoveryMode = DiscoveryMode.Always)] )

Collection Imports

  • New ImportManyAttribute replaces ImportAttribute on collections in the future.
  • Array imports are now supported

Typed Import/Exports

  • Imports and Exports now match on type as well as contract.
  • Exporters of string contracts such  as [Export(“Foo”)] must now specify the type they expect to be imported as well. e.g. [Export(“Foo”, typeof(string))]

Custom delegates

  • Method exports can now use custom delegates in addition to Action and Func

Directory Catalog changes

  • DirectoryCatalog no longer has watching functionality. We’ve added a Refresh() method for explicit refresh.

Removal of caching / new infrastructure

  • Old caching infrastructure has been ripped. We’ve added a new more general purpose API that is more flexible, and supports builders of custom programming models. We are not shipping a default caching implementation of that API in the box, but one is available in our samples.
   

MEF Studio

MEF Studio is a designer hosting sample. It has an IDE that hosts various designers like Windows Forms, UserControl, etc. One can add controls on to these designers from the toolbox, set properties using the property grid, view generated code, etc. It provides design time experience for the designers and has extension points for extending the adding custom parts.

image

Migrating from preview 4

When you upgrade your existing apps, you will likely run into a few issues. Wes put together the following useful list of common errors that you might run into when migrating.

  • System.ComponentModel.Composition.Container does not exist -> Add reference to System.ComponentModel.Composition.Hosting namespace
  • CompositionContainer does not container method AddPart or Compose -> Need to start using CompositionBatch, or one of the helper extension methods ComposeParts or ComposeExportedObjects
  • CompositionOptionsAttribute does not exist -> For CreationPolicy use PartCreationPolicyAttribute
  • INotifyImportCompleted does not exist -> Use IPartImportsSatisfiedNotification interface and change method from ImportCompleted to OnImportsSatisfied

Part Lifetime changes

Previously, the CreationPolicy property of the CompositionOptions attribute was used to declare a part’s lifetime. We found this was less discoverable for customers as they have to know about the existence of the attribute. We also found that CreationPolicy was ambiguous in name, as it does not make clear whether or not the policy applies to the part or the export.

With the new changes, you will use the new PartCreationPolicyAttribute. CompositionOptions has been removed. Having part in the name of the attribute makes it easier to discover, and also removes ambiguity as to it’s scope.

Below the Logger class is declared as shared:

 [PartCreationPolicy(CreationPolicy.Shared)]
[Export(typeof(ILogger))]
public class Logger : ILogger
{
}

Part Discovery / Inheritance

In MEF Preview 4, a part by default would be discovered if there was an export on the most derived class. Additionally a part author could decorate the part with the CompositionOptions attribute, and set the DiscoveryMode property to change the behavior. Regardless of which mode was chosen, we would always look at all exports in the inheritance chain and discover them once we determined a part was a part.

We heard quite a bit of feedback both internally and externally around both the naming of the different enum values for DiscoveryModel, and the resulting behavior.

Default Behavior

By default we will only look only at the most derived type to discover exports. If exports are found, then we will do a deep dive to discover all imports wherever they live in the hierarchy, including interface members. This is because imports are an implementation detail of a part, and the part many not function without them.

In the example, we will discover a single export of IOrderScreen, and the OrderList import.

 [Export(typeof(IOrderScreen))]
public abstract class OrderScreen : IOrderScreen
{
 [Import]
 public IOrderListView OrderList { get; set; }
}

[Export(typeof(IOrderScreen))]
public class NorthwindOrderScreen : OrderScreen
{
}

Below, we will not discover anything, as the export is on the base.

 [Export(typeof(IOrderScreen))]
public abstract class OrderScreen : IOrderScreen
{
}

public class NorthwindOrderScreen : OrderScreen
{
 [Import]
 public IOrderListView OrderList { get; set; }
}

Inherited exports behavior – using the PartExportsInheritedAttributed

The PartExportsInherited attribute allows declaring on a base class or an interface that it’s exports should be discovered by inheritors / implementers. This replaces the previous [CompositionOptions(DiscoveryMode = DiscoveryMode.Always)]. This basically allows you to define a template for a part, and is useful hiding MEF’s implementation details from a part author. Implementers can further extend on what has been provided, such as adding their own exports and imports.

There’s a few guidelines / caveats to using this feature.

  • PartExportsInheritedAttribute only makes the exports of the type it decorates discoverable by inheritors. It does not make exports of derivers of that type discoverable. The deriver itself needs to be decorated in order to make its exports discoverable.
  • We will ignore loose ExportMetadata attributes (and custom metadata attributes) that do not have an associated export.
  • If an inheritor overrides a member from the base which has an export, and decorates the overridden member with an export, two exports will be discovered, even if the contract names are the same. This means if you expect implementers to add their own metadata, this might not be the best option, or at least you will need to provide special handling. We are looking into changing this behavior to have the inheritor override rather than be additive.

In the example below, NorthwindOrderScreen will automatically export IOrderScreen.

 [PartExportsInherited]
[Export]
public interface IOrderScreen
{
}

public class NorthwindOrderScreen : IOrderScreen
{
}

Below NorthwindOrderScreen exports 2 instances of IOrderScreen, one with metadata and one without. The explicit export of IOrderScreen by NorthwindOrderScreen is in addition to the existing export.

[PartExportsInherited]
[Export]
public interface IOrderScreen
{
}

[ExportMetadata("Customer", "Northwind"]
[Export(typeof(IOrderScreen))]
public classNorthwindOrderScreen :IOrderScreen
{
}

Ignoring part discovery – Using the PartNotDiscoverableAttribute

This attribute replaces replaces [CompositionOptions(DiscoveryMode.Never).  It is useful for when a part author has parts that they do not want to be automatically discovered by catalogs and .  The scenarios where this make sense are where you either have a part which you will manually compose outside of a catalog, or you have a part that you intend for inheritors to use in a catalog but which cannot be made abstract.

In the sample below,  class OrderScreen will be ignored by the catalog. However, when class NorthwindOrderScreen is scanned, we will discover IOrderScreen, and will satisfy the OrderList import.

 [PartNotDiscoverable]
[PartExportsInherited]
[Export(typeof(IOrderScreen))]
public class OrderScreen : IOrderScreen
{
 [Import]
 public IOrderListView OrderList { get; set; }
}

public class NorthwindOrderScreen : OrderScreen
{
}

Changes to collection imports

ImportMany attribute

We’ve added a new [ImportMany] attribute. This attribute is to be used for specifying that an importer is expected to import all parts that match the contract. This attribute is equivalent to using [Import] on collections today. By our next release however, [Import] will change to mean import an exported collection.

Once we make the switch, the [ImportMany] attribute will be required on both properties and importing constructor parameters in order to get the same behavior as today.

As an example let’s take an importer of a collection of IOperation contacts.

With the new build the following will have identical results.

 [Import]
public IEnumerable<IOperation> Operations { get; set; }

[ImportMany]
public IEnumerable<IOperation> Operations { get; set; }

In the next drop,  the behavior of the first will change. Instead of importing all exports of IOperation, it will instead be looking for an exporter of IEnumerable<IOperation>. The main reason for this is to support custom collections. Today for example the following code won’t yield the expected results if IRepository<T> is an ICollection<T>.

 [Import]
public IRepository<Customer> Customers { get; set; }

What the part author is intending here is to import a repository of customers. The repository as a whole is the import. Instead what will happen today is the container will set Customers to a new instance of Repository<Customer>. It will then query for all the Customer exports and add them to the Repository.

Array imports

In this release we’ve added support for array imports.  This allows the following syntax to be supported.

 [ImportMany]
public IOperation[] Operations { get; set; }

Future Deprecation of ExportCollection<T> and ExportCollection<T,M>

In the next preview (not this one) we will be removing ExportCollection from MEF. The reasoning is that ExportCollection does not introduce any additional functionality, and we don’t want to introduce a collection which is solely there for syntactic sugar. The alternative recommended syntax for doing ExportCollection will be to use the new array support. Using the new syntax is significantly more compact than using ExportCollection, and is also less cryptic as arrays are very clear as to how they function.

 [ImportMany]
public Export<IOperation>[] Operations { get; set; }

[ImportMany]
public Export<IOperation, IOperationMetadata>[] Operations { get; set; }

Additionally we support custom derived collections, thus you can easily create your own collections that provide the sugar.

Typed Import/Exports

In Preview 4, MEF discovers exports based on contract name, and not based on the type of the exporter. This means that an export can actually have an incompatible type with the importer. At the time when the export is set on the import, if the cast fails, then an exception is thrown.

This new feature in Preview 5 enables MEF to match imports and exports on contract AND type. When a part imports a contract, it additionally specifies the specific type that it is looking for. Only those exporters that also match on the importer type are matched. This means instead of failing, if the exporter’s type is incompatible, then it will not be matched.

In most cases, the type specification is done implicitly, and does not require any work on the part of the part author. The overloads of Export and Import that used to take Type as param, will now in addition to converting to the string contract representation, also embed the type information.

For example the code below works as it would have before.

 [Export]
public class Editor
{
 [Import]
 public ISpellChecker SpellChecker { get; set; }
}

[Export(typeof(ISpellChecker))]
public class SpellChecker : ISpellChecker
{
}

However code that uses pure string contracts, will yield a different result than it would have before. The SpellChecker below will not be imported into the Editor.

 [Export]
public class Editor
{
 [Import("SpellChecker")]
 public ISpellChecker SpellChecker { get; set; }
}

[Export("SpellChecker")]
public class SpellChecker : ISpellChecker
{
}

Although SpellChecker implements ISpellChecker the type that the export carries is SpellChecker. The importer on the other hand is of type ISpellChecker so they do not match. In order to make the above code work, the export needs to specify the type.

 [Export("SpellChecker",typeof(ISpellChecker))]
public class SpellChecker : ISpellChecker
{
}

This works as long as the type of the importer is ISpellChecker. Another option is for the importer to lift the restriction altogether by specifying the type as object.

 [Export]
public class Editor
{
 [Import("EditorContracts.SpellChecker",typeof(object))]
 public EditorContracts.ISpellChecker SpellChecker { get; set; }
}

Now SpellChecker can be imported by any importer with the same contract name as long as the importer is either a SpellChecker or a derived type.

Custom delegates

In Preview 4, we only support Func and Action delegates for exports. Back by popular demand, we now support custom delegates again as we did in our earlier previews. This means that when you export a method, you can use a custom delegate, as opposed to having to use one of the pre-baked ones. For example in the code below, the Validate method is exporting ValidatorDelegate<Customer> which CustomerView is importing. String contracts are also fully supported.

 public delegate bool ValidatorDelegate<T>(T value);

public class CustomerValidator
{
  [Export(typeof(ValidatorDelegate<Customer>))]
  public bool Validate(Customer customer)
  {
    return customer.Name != null && customer.Name != String.Empty;
  }
}
 [Export]
public class CustomerView
{
  [ImportingConstructor]
  public CustomerView(ValidatorDelegate<Customer> validator)
  {
  }
}

Directory Catalog changes

Prior versions of the DirectoryCatalog internally used a FileSystemWatcher in order to notify the catalog that changes have occurred within the watched directory. Using the watcher was problematic for many reasons usually related to MEF trying to grab the file too early. Today it is very common for apps that enable the watcher to fail whenever a file is dropped in the folder.

As such we removed the watching functionality and replaced it with an explicit Refresh() method. The advantage of the refresh is it allows the host to determine when the refresh should occur. For example, a .NET Reflector type app could have a refresh button on the addins screen, the rescans the folders on demand. Once the folder is scanned, the catalog will do the delta to determine any changes, and it will fire change notifications as it does today, thus triggering re-composition in the container.

 public class Application
{
  private DirectoryCatalog catalog;

  //triggered by the user clicking the Refresh button on the addins page
  public void Refresh()
  {
    catalog.Refresh();
  }
}

Removal of caching / new infrastructure

We found several issues with our previous caching design which was black box. We’ve completely removed the old caching apis. We’ve added in a new API that can be used for caching, however we are not including a default caching implementation within MEF itself. We will however be shipping a caching API sample which illustrates how to use the APIs. The new APIs provides the further benefit of making it much easier to build new programming models that utilize reflection for are reflection based.

We’ve now introduced a new set of classes in System.ComponentModel.Composition.ReflectionModel that can be used by cache implementers. ReflectionModelServices provides a set of static helper methods that can be used for manufacturing part definitions from a cache store.

Summary

A lot of new things to play with in preview 5. My personal favorite is the support we’ve now added for exports on interfaces, though I am sure if you ask different members of the team you’ll get a different answer.

Thanks as always to the team for all their efforts, and thank you for your continual patience and feedback. We’re getting closer and closer to RTM!

So what are you waiting for? Go download MEF Preview 5 here.