The Code:
namespace FSharpIoC open System open System.Collections.Generic open System.Reflection module FSharpIoCModule = let rec Resolve requestedType (typeContainer:Dictionary<Type,Type>) = let newType = typeContainer.[requestedType] let constructors = newType.GetConstructors() |> Array.sortBy (fun c -> c.GetParameters().Length) |> Array.rev let theConstructor = constructors.[0] match theConstructor.GetParameters() with | cstorParams when cstorParams.Length = 0 -> Activator.CreateInstance(newType) | cstorParams -> cstorParams |> Seq.map(fun (paramInfo:ParameterInfo) -> (Resolve paramInfo.ParameterType typeContainer)) |> Seq.toArray |> theConstructor.Invoke type Container = val typeContainer : Dictionary<Type, Type> new () = {typeContainer = new Dictionary<Type, Type>()} member x.Register<'a,'b> () = x.typeContainer.Add(typeof<'a>, typeof<'b>) member x.Resolve(requestedType) = FSharpIoCModule.Resolve requestedType x.typeContainer member x.Resolve<'a> () = FSharpIoCModule.Resolve typeof<'a> x.typeContainer :?> 'aUsing Our IoC Container From F#:
The code below is not exactly a "real world" example, but it does show how our IoC container could be used in an F# application:
module FSharpIoCExample open System open FSharpIoC type Person = { FirstName : string LastName : string} type IPersonFactory = abstract CreatePerson : unit -> Person type PersonFactory() = interface IPersonFactory with member x.CreatePerson () = {FirstName = "TestFirst"; LastName = "TestLast"} type PersonService = val personFactory : IPersonFactory new (personFactory) = {personFactory = personFactory} member x.GetPerson () = x.personFactory.CreatePerson() let iocContainer = new Container() do iocContainer.Register<IPersonFactory, PersonFactory>() do iocContainer.Register<PersonService, PersonService>() let personService = iocContainer.Resolve<PersonService>() let person = personService.GetPerson() do Console.WriteLine("The person's name is {0} {1}", person.FirstName, person.LastName) do Console.ReadLine()Using Our IoC Container From C#:
Here is the same example in C#:
using System; using FSharpIoC; namespace CSharpIoCExample { class Program { static void Main(string[] args) { var iocContainer = new Container(); iocContainer.Register<IPersonFactory, PersonFactory>(); iocContainer.Register<PersonService, PersonService>(); var personService = iocContainer.Resolve<PersonService>(); var person = personService.GetPerson(); Console.WriteLine("The person's name is {0} {1}", person.FirstName, person.LastName); Console.ReadLine(); } } public class Person { public string FirstName { get; set; } public string LastName { get; set; } } public interface IPersonFactory { Person CreatePerson(); } public class PersonFactory : IPersonFactory { public Person CreatePerson() { return new Person { FirstName = "TestFirst", LastName = "TestLast" }; } } public class PersonService { protected readonly IPersonFactory _personFactory; public PersonService(IPersonFactory personFactory) { _personFactory = personFactory; } public Person GetPerson() { return _personFactory.CreatePerson(); } } }Conclusion
As you can see, it doesn't take much to build a slim featured IoC container in F#. You can find the full source at http://github.com/dmohl/FSharpIoC.
Great post - it's really helpful to have posts connecting terminology and code like this.
ReplyDeleteBTW why not use implicit construction for PersonService?
type PersonService(personFactory: IPersonFactory) =
member x.GetPerson () = personFactory.CreatePerson()
That's a great point! Implicit construction is a more succinct way to accomplish this functionality.
ReplyDeleteThat's a great point! Implicit construction is a more succinct way to accomplish this functionality.
ReplyDelete