Monday, December 1, 2008

A quick look at MEF with F#

In this post I'm going to show a little example of using the Managed Extensibility Framework (MEF) from F#.

The Managed Extensibility Framework (MEF) is a very interesting framework for building extensible applications. It is planned to be used in products such as Visual Studio 2010.

Episode .NET Rocks #398: Glenn Block on MEF is a nice source of information the project.

The MEF Codeplex page has documentation on how to start using it. Also the Addicted to MEF series is a nice source of information on how to start using it.

The example



For this example I want to define a little program F# program that plots math functions. Function definitions will be represented as classes that implement the IFunction interface. These definitions will be taken from DLLs stored in a "extensions" directory.

Here's the definition of the IFunction interface:


type IFunction = interface
abstract Apply : double -> double
end



The following code shows the definition of the Winforms Form that is used to display the function:


open System
open System.Windows.Forms
open System.Drawing
open FunctionDefinitions
open System.ComponentModel.Composition
open System.Collections.Generic

type MyForm() as this = class
inherit System.Windows.Forms.Form()

let mutable functions : IEnumerable<IFunction> = Seq.empty

do this.Size <- new System.Drawing.Size(300,300)
do this.BackColor <- Color.White
do this.Text <- "Functions"
do this.Paint.Add(
fun (pargs:PaintEventArgs) ->
using(new Pen(Brushes.Black))
(fun(p) -> pargs.Graphics.DrawLine(p,
new Point(this.Size.Width/2,0),
new Point(this.Size.Width/2,this.Size.Height))

pargs.Graphics.DrawLine(p,
new Point(0,this.Size.Height/2),
new Point(this.Size.Width,this.Size.Height/2))
Seq.iter (fun func -> pargs.Graphics.DrawLines(p,this.CalcPoints(func))) functions
))

member this.CalcPoints(func:IFunction) : Point array =
{ 0..300} |> Seq.map (fun x -> -1.0/30.0 * double(x) + 5.0) |>
Seq.map (fun x -> x,func.Apply(x)) |>
Seq.map (fun(x,y) -> new Point(int32(x*30.0+150.0),
int32(y*(-30.0)+150.0))) |>
Seq.to_array

[<Import>]
member this.Functions
with get() : IEnumerable<IFunction> = functions
and set (f : IEnumerable<IFunction>) = functions <- f

end


In order to say that we want all available functions(IFunction) identified by MEF we declare the Functions property with type IEnumerable<IFunction> and decorated with the Import attribute.

The following code shows the how we use MEF to compose the application and run the winforms app:


let f = new MyForm()
let catalog = new DirectoryPartCatalog("c:\\temp\\Extensions")
let container = new CompositionContainer(catalog,[||])

container.AddPart(f)
container.Compose()

System.Windows.Forms.Application.Run(f)


Notice that by using DirectoryPartCatalog we say that we want to take all extensions(math function definitions) stored in c:\temp\Extensions.

It is important to notice that the program that has the Winforms application is stored in a different assembly from where IFunction is defined. The reason is that the extension DLLs need a reference to it.

Running this program without extensions shows the following form:




The following code shows an extension written in F#:


#light

namespace MoreFunctions
open FunctionDefinitions
open System.ComponentModel.Composition

[<Export(typeof<IFunction>)>]
type AFunction() = class
interface IFunction with
member this.Apply(x:double) = System.Math.Sin(x)
end


We compile this file and copy the result to the extensions directory.


fsc.exe other.fs -r FunctionDefinitions.dll -r System.ComponentModel.Composition.dll -a
copy other.dll c:\temp\Extensions


By running the program again the following form is shown




Since we're working with common .NET assemblies we can write a function definition using C#, for example:



using FunctionDefinitions;
using System.ComponentModel.Composition;
namespace Test2 {
[Export(typeof(IFunction))]
public class AFunction : IFunction
{
public double Apply(double d)
{
return d*d;
}
}
}


We compile this file and copy it to the extensions directory:


csc /reference:FunctionDefinitions.dll;System.ComponentModel.Composition.dll /t:library AFunc.cs
copy AFunc.dll c:\temp\Extensions



Running this program shows the following form:




Final words



MEF provides a nice/simple way to add extensions to .NET applications. I was really impressed by how simple it is to define Exports and Imports and how little MEF-specific code you have to write in order to make it work.

One thing that will be interesting to see is how this framework is going to interact with things such as the DLR. A nice example of this is Intellipad which, as mentioned in the .NET Rocks interview, is used to allow IronPython extensions.

Code for this post uses MEF Preview 3 release.

3 comments:

MichaelGG said...

Does MEF not support loading functions directly, i.e., you MUST create an IFunction interface type?

Luis Diego Fallas said...

Good question!

I think there's a couple of ways to change this code to export and import a single function.

To still allow the creation of C# functions we can change the type of the type of the import to use System.Func<double,double> instead of IFunction:

[<Import("Foo")>]
member this.Functions
with get() : IEnumerable<Func<double,double>> = functions
and set (f : IEnumerable<Func<double,double>>) = functions <- f

An export in F# looks like this:

module Functions = begin
[<Export("Foo")>]
let goo(x:double) = x*x*x*x
end

An export in C# looks like this:

[Export("Foo")]
public static double ANewFunc(double d)
{
return System.Math.Sin(d*d);
}


Another alternative is to use a more appropriate F# type for the functions like "double->double":

[<Import>]
member this.Functions
with get() : IEnumerable<double->double> = functions
and set (f : IEnumerable<double->double>) = functions <- f

An F# export looks like this:

module Functions = begin
let goo(x:double) = x*x*x*x
[<Export>]
let exportedFunction = goo
end

However I had some trouble creating a C# function that could be exported using this type.

M said...

Luis, that's good of MEF then! To get the F# style function, you'd probably have to create a C# field and call the FuncConvertExtensions.ToFastFunc on the method.