Yesterday on twitter I was expressing how far behind I am on blogging, and how I don’t know where to start. Ayende (Oren) responded by saying “Just start bloggging!” So here goes.
Ben Hall pinged me over IM yesterday asking about recomposable imports on constructors, and whether or not they work? Natively MEF does not support this, but I did find a workaround. More about this later as the answer won’t make any sense without first understanding what in the heck recomposition is.
Recomposition is an opt-in feature of MEF that allows imports to be updated whenever there are changes in the container that relate to the availability of exports of that contract, you can think of this as live imports.
Recomposition is one of those capabilities that is useful for systems that dynamically change after the app is running. The change does not only have to be due to new assemblies appearing, it could due to contextual changes in the application such as a user updating their profile, or the application moving into a different state.
For example below I have a part that imports a collection of ILoggers.
Notice the import is marked with AllowRecomposition = true. This means that after initial composition, the Loggers collection should be updated should new loggers either be manually added (or removed) to the container, or appear in a catalog.
Recomposition is not only useful for collections, it can also be for single imports, which was Ben’s case. That is I could have a single logger import which is also recomposable. That means that if someone replaces the single logger in the system, then the part which imports will automatically be updated.
Now the OrderProcessor expects to see a single ILogger. That logger however is recomposable such that when the logger is replaced, it will get automatically updated.
Now that we understand what Recomposition is, we can get to Ben’s question. Are constructors parameters supported? The answer is no (at least not without help). The reason is that recomposition on MEF is connected to the part,. Whenever a part has imports that are recomposable, we keep a pointer to the associated ComposablePart within the container. We then monitor changes on that particular contract that the part is importing. Whenever changes occur, we then update the imports on the part which results in replacing the value of the corresponding property. This works because we can replace the reference. In the case of constructor parameters however, we cannot grab your parameter reference and change it.
OK, so we’re out of luck then?
Initially I thought yes, but then as I thought about it, I realized there is a workaround. That is create a generic Recomposable<T> which itself has an import of T that is recomposable. The class is so simple, it’s not even funny, and here it is.
Now you can do an import such as the following:
The logger in UsesLogger is now recomposable.
Now there is one caveat. Because MEF does not support open generics (see this thread for more on that), you need to either create closed versions of Recomposable<T> which export the correct contract and have them discovered in a catalog:
Or you need to use a TypeCatalog and manually add each type that you want to be able to import:
The first approach is what extenders need to do if they were introducing new types that the main app does not now about.
With great power comes responsibility, proceed with caution!
Some of you might be rolling your eyes at this post and thinking, does he expect me to believe that this will just automagically work? And the answer is……NO!
Recomposition does not come for free. I mean you can turn it on for free, but you have to design for it. When we recompose, we completely replace the property reference. There is no inherent thread-safety in this, it just happens (if you opt-in), and you need to prepare for it, and design with recomposition in mind. This means very carefully deciding where to use recomposition, and making sure where it is used, the proper safeguards are put in place. And that could probably be a whole other post.
See it in action
For a sample of recomposition with an overriding logger, see this sample code.