Writing big software is hard. Customers want customization, and writing big extensible software is harder still. OOP has been promising extensibility and reusability, so this should be easier now right? Well, no, but we can force the issue and bring vast extensibility with a pleasant syntax. We’re going to do this using the Managed Extensibility Framework (MEF) that was released as part of .NET Framework 4.0. But first let’s decide if you care or not by answering the following questions:
- Do you have a set of core libraries that are used to provide functionality throughout several different applications?
- Do your customers ask for customizations but the thought of maintaining several divergent implementations causes your stomach to churn?
- Do you need to deploy new functionality that doesn’t fit in a patch but isn’t really worth an entire release?
- Do your customers sometimes say, “I just want [small functionality enhancement ‘X’]; can I write some sort of plugin?”
- Are you using .NET?
- Are you interested in acronyms consisting of 3 letters?
If you answered yes to more than one of these, you and MEF will get along just fine.
MEF is the new extensibility framework for .NET. As Microsoft puts it, “Using MEF, .NET applications can make the shift from being statically compiled to dynamically composed.” With MEF, programs import extenders (i.e., plug-ins) by stating what they need, extenders export discoverable parts, and MEF puts it all together (i.e. composes it). Now that Synergy Language is a .NET language, MEF is available to be used in your Synergy applications. Just add a reference in your Synergy project to the System.ComponentModel.Composition assembly, and start importing, exporting, and composing with MEF.
In the snippets that follow, we’ll make a class called Program that logs its actions. We will use MEF to enable Program to be extended without it knowing anything about its extenders and without its extenders knowing anything about Program. Let’s dive in by breaking down a simple snippet:
class Program
{Import}
Logger, @ILogger
endclass
The first part is pretty normal; we’re just declaring a class named Program. Then we get to the Import attribute on a field named Logger of the type ILogger. The purpose of this attribute is to let MEF know that we’re looking for an ILogger and, (if it would be so kind) we would really appreciate it if it would provide us with one. So we’re done right? We ask for a logger, MEF kindly gives us one, and it just does whatever we’ve imagined an implementation of ILogger would do. Err…ummm, that’s not quite right. Let’s try that again. MEF only knows types we’ve told it about. So here’s another snippet to break down:
{Export(^typeof(ILogger))}
public class ConsoleLogger implements ILogger
public method Log, void
message, @string
proc
Console.WriteLine(message);
endmethod
endclass
Given that an Import attribute tells MEF we want something, Export tells MEF we want to give it something. But Export also needs to know more specifically what we want to expose, so we pass it the ILogger type.
Now we’ve got something that imports and something that exports, so we must be done. Well not quite. Someone needs to tell MEF that we want to hook what we’ve exported to what we’ve imported. I feel another snippet coming on:
public static method MakeProgram, @Program
record
madeProgram, @Program
catalog, @AssemblyCatalog
container, @CompositionContainer
proc
madeProgram = new Program()
catalog = new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly())
container = new CompositionContainer(catalog, new ExportProvider[0])
AttributedModelServices.ComposeParts(container, new object[#] { madeProgram })
mreturn madeProgram
endmethod
We start off specifying a static method that returns a new instance of Program. Once we make the basic instance, we need to get a catalog of all the available MEF parts. In this case we’re going to look at the current assembly since that’s where everything resides this simple example. Once we’ve got our parts we can make a CompositionContainer and use ComposeParts to put the pieces togher as madeProgram.
Wow, that looks like a lot more effort than just setting Logger to be a new ConsoleLogger. I’m not going to dance around it; it is a lot more effort. But it’s worth it since the code can be made to work with a lot more than just Program and ConsoleLogger.
So now we’ve got Logger being composed as a ConsoleLogger, but what if ConsoleLogger wants to import something. Well, that may sound like it’s going to get complex, but it’s not. It actually gets easier since all we have to do is Import/Export what we want. Then, when ConsoleLogger gets instantiated, MEF calls ComposeParts for us. This makes it easy for your main application to provide services that can easily be consumed by extensions. It also makes it easy for your extensions to use default implementations or take advantage of other extensions in order to do their work. There are many more things you can do with MEF. I would recommend taking a look at the MEF Programming Guide for a more-than-cursory glance at MEF.
[Code Listing]
import System
import System.Reflection
import System.ComponentModel.Composition
import System.ComponentModel.Composition.Hostingnamespace SENMEF
main
proc
Program.MakeProgram().DoStuff()
endmain
class Program
{Import}
protected Logger, @ILogger
public static method MakeProgram, @Program
record
madeProgram, @Program
catalog, @AssemblyCatalog
container, @CompositionContainer
proc
madeProgram = new Program()
catalog = new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly())
container = new CompositionContainer(catalog, new ExportProvider[0])
AttributedModelServices.ComposeParts(container, new object[#] { madeProgram })
mreturn madeProgram
endmethod
public method DoStuff, void
proc
Logger.Log("Hello Extensibility")
endmethod
endclass
public interface ILogger
method Log, void
message, @string
endmethod
endinterface
public interface IConsole
method Print, void
text, @string
endmethod
endinterface
{Export(^typeof(IConsole))}
public class SystemConsole implements IConsole
public method Print, void
text, @string
proc
Console.WriteLine(text)
endmethod
endclass
{Export(^typeof(ILogger))}
public class ConsoleLogger implements ILogger
{import}
TheConsole, @IConsole
public method Log, void
message, @string
proc
TheConsole.Print(message);
endmethod
endclass
endnamespace