13 people like it.

Mini IoC Container

Minimal Inversion of Control (IoC) Container for Dependency Injection (DI) in under 100 lines of code. Implements the 3 Rs of DI: Register, Resolve, Release. Note: missing thread safety and fluent interface.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
45: 
46: 
47: 
48: 
49: 
50: 
51: 
52: 
53: 
54: 
55: 
56: 
57: 
58: 
59: 
60: 
61: 
62: 
63: 
64: 
65: 
66: 
67: 
68: 
69: 
70: 
71: 
72: 
73: 
74: 
75: 
76: 
77: 
78: 
79: 
80: 
81: 
82: 
83: 
84: 
85: 
86: 
87: 
88: 
89: 
90: 
91: 
92: 
93: 
94: 
95: 
96: 
97: 
98: 
open System
open System.Collections.Generic

type Lifetime = Singleton | Transient

type private Constructor =
    | Reflected of Type
    | Factory of (unit -> obj)

type Container () =
    let container = Dictionary<Type,Constructor * Lifetime>()
    let singletons = Dictionary<Type,obj>()
    let rec resolve t =
        match container.TryGetValue(t) with
        | true, (Factory f, lifetime)-> 
            obtain t (fun () -> f() |> Some) lifetime
        | true, (Reflected u, lifetime) ->
            obtain t (fun () -> construct u) lifetime
        | false, _ -> 
            obtain t (fun () -> construct t) Singleton 
    and obtain t f lifetime =
        match singletons.TryGetValue t with
        | true, value -> Some value
        | false, _ ->
            let result = f()
            result |> Option.iter (fun value -> store lifetime t value)
            result
    and store lifetime t value =
        match lifetime with
        | Singleton -> singletons.Add(t,value)
        | Transient -> ()
    and construct t =
        t.GetConstructors() |> Array.tryPick (fun ctr ->
            let ps = ctr.GetParameters()
            let args = ps |> Array.map (fun p -> resolve p.ParameterType)
            match args |> Array.forall Option.isSome with
            | true -> args |> (Array.choose id) |> ctr.Invoke |> Some
            | false -> None
        )
    member this.Register<'TAbstract, 'TConcrete 
                          when 'TAbstract : not struct 
                          and 'TConcrete : not struct> 
                          (lifetime:Lifetime) =
        let ctr = Reflected(typeof<'TConcrete>)
        container.Add(typeof<'TAbstract>, (ctr,lifetime))
    member this.Register<'TAbstract, 'TConcrete 
                          when 'TAbstract : not struct 
                          and 'TConcrete : not struct>() = 
        this.Register<'TAbstract,'TConcrete>(Singleton)
    member this.Register<'TAbstract
                          when 'TAbstract : not struct>
                          (f:unit->'TAbstract, lifetime:Lifetime) = 
        container.Add(typeof<'TAbstract>, (Factory(f >> box), lifetime))
    member this.Register(f:unit->'TAbstract) = 
        this.Register(f, Singleton)
    member this.Resolve<'TAbstract when 'TAbstract : not struct>() =
        let t = typeof<'TAbstract>
        match resolve t with
        | Some value -> value :?> 'TAbstract
        | None -> sprintf "Failed to resolve %A" t |> invalidOp
    member this.Release(instance:obj) =
        singletons 
        |> Seq.filter (fun pair -> pair.Value = instance)
        |> Seq.toList
        |> List.iter (fun pair -> singletons.Remove(pair.Key) |> ignore)

module Usage =
    
    let container = new Container()
    
    type A () =
        do  printfn "A"

    type B () =
        do  printfn "B"

    type C (a:A, b:B) =
        do  printfn "C"

    container.Register((fun () -> printfn "B lambda"; B()), Transient)
    container.Register<C,C>()
    let c = container.Resolve<C>()
    let c' = container.Resolve<C>()
    container.Release(c)
    let c'' = container.Resolve<C>()

    type ICalculate =
        abstract member Incr : int -> int

    type Calculator () =
        interface ICalculate with
            member this.Incr(x:int) = x + 1
    
    container.Register<ICalculate, Calculator>()

    let calc = container.Resolve<ICalculate>()
    printfn "%d" (calc.Incr 1)
    Console.ReadLine() |> ignore
namespace System
namespace System.Collections
namespace System.Collections.Generic
type Lifetime =
  | Singleton
  | Transient

Full name: Script.Lifetime
union case Lifetime.Singleton: Lifetime
union case Lifetime.Transient: Lifetime
type private Constructor =
  | Reflected of Type
  | Factory of (unit -> obj)

Full name: Script.Constructor
union case Constructor.Reflected: Type -> Constructor
type Type =
  inherit MemberInfo
  member Assembly : Assembly
  member AssemblyQualifiedName : string
  member Attributes : TypeAttributes
  member BaseType : Type
  member ContainsGenericParameters : bool
  member DeclaringMethod : MethodBase
  member DeclaringType : Type
  member Equals : o:obj -> bool + 1 overload
  member FindInterfaces : filter:TypeFilter * filterCriteria:obj -> Type[]
  member FindMembers : memberType:MemberTypes * bindingAttr:BindingFlags * filter:MemberFilter * filterCriteria:obj -> MemberInfo[]
  ...

Full name: System.Type
union case Constructor.Factory: (unit -> obj) -> Constructor
type unit = Unit

Full name: Microsoft.FSharp.Core.unit
type obj = Object

Full name: Microsoft.FSharp.Core.obj
Multiple items
type Container =
  new : unit -> Container
  member Register : unit -> unit
  member Register : lifetime:Lifetime -> unit
  member Register : f:(unit -> 'TAbstract) -> unit (requires reference type)
  member Register : f:(unit -> 'TAbstract) * lifetime:Lifetime -> unit (requires reference type)
  member Release : instance:obj -> unit
  member Resolve : unit -> 'TAbstract (requires reference type)

Full name: Script.Container

--------------------
new : unit -> Container
val container : Dictionary<Type,(Constructor * Lifetime)>
Multiple items
type Dictionary<'TKey,'TValue> =
  new : unit -> Dictionary<'TKey, 'TValue> + 5 overloads
  member Add : key:'TKey * value:'TValue -> unit
  member Clear : unit -> unit
  member Comparer : IEqualityComparer<'TKey>
  member ContainsKey : key:'TKey -> bool
  member ContainsValue : value:'TValue -> bool
  member Count : int
  member GetEnumerator : unit -> Enumerator<'TKey, 'TValue>
  member GetObjectData : info:SerializationInfo * context:StreamingContext -> unit
  member Item : 'TKey -> 'TValue with get, set
  ...
  nested type Enumerator
  nested type KeyCollection
  nested type ValueCollection

Full name: System.Collections.Generic.Dictionary<_,_>

--------------------
Dictionary() : unit
Dictionary(capacity: int) : unit
Dictionary(comparer: IEqualityComparer<'TKey>) : unit
Dictionary(dictionary: IDictionary<'TKey,'TValue>) : unit
Dictionary(capacity: int, comparer: IEqualityComparer<'TKey>) : unit
Dictionary(dictionary: IDictionary<'TKey,'TValue>, comparer: IEqualityComparer<'TKey>) : unit
val singletons : Dictionary<Type,obj>
val resolve : (Type -> obj option)
val t : Type
Dictionary.TryGetValue(key: Type, value: byref<Constructor * Lifetime>) : bool
val f : (unit -> obj)
val lifetime : Lifetime
val obtain : (Type -> (unit -> obj option) -> Lifetime -> obj option)
union case Option.Some: Value: 'T -> Option<'T>
val u : Type
val construct : (Type -> obj option)
val f : (unit -> obj option)
Dictionary.TryGetValue(key: Type, value: byref<obj>) : bool
val value : obj
val result : obj option
module Option

from Microsoft.FSharp.Core
val iter : action:('T -> unit) -> option:'T option -> unit

Full name: Microsoft.FSharp.Core.Option.iter
val store : (Lifetime -> Type -> obj -> unit)
Dictionary.Add(key: Type, value: obj) : unit
Type.GetConstructors() : Reflection.ConstructorInfo []
Type.GetConstructors(bindingAttr: Reflection.BindingFlags) : Reflection.ConstructorInfo []
type Array =
  member Clone : unit -> obj
  member CopyTo : array:Array * index:int -> unit + 1 overload
  member GetEnumerator : unit -> IEnumerator
  member GetLength : dimension:int -> int
  member GetLongLength : dimension:int -> int64
  member GetLowerBound : dimension:int -> int
  member GetUpperBound : dimension:int -> int
  member GetValue : [<ParamArray>] indices:int[] -> obj + 7 overloads
  member Initialize : unit -> unit
  member IsFixedSize : bool
  ...

Full name: System.Array
val tryPick : chooser:('T -> 'U option) -> array:'T [] -> 'U option

Full name: Microsoft.FSharp.Collections.Array.tryPick
val ctr : Reflection.ConstructorInfo
val ps : Reflection.ParameterInfo []
Reflection.MethodBase.GetParameters() : Reflection.ParameterInfo []
val args : obj option []
val map : mapping:('T -> 'U) -> array:'T [] -> 'U []

Full name: Microsoft.FSharp.Collections.Array.map
val p : Reflection.ParameterInfo
property Reflection.ParameterInfo.ParameterType: Type
val forall : predicate:('T -> bool) -> array:'T [] -> bool

Full name: Microsoft.FSharp.Collections.Array.forall
val isSome : option:'T option -> bool

Full name: Microsoft.FSharp.Core.Option.isSome
val choose : chooser:('T -> 'U option) -> array:'T [] -> 'U []

Full name: Microsoft.FSharp.Collections.Array.choose
val id : x:'T -> 'T

Full name: Microsoft.FSharp.Core.Operators.id
Reflection.ConstructorInfo.Invoke(parameters: obj []) : obj
Reflection.MethodBase.Invoke(obj: obj, parameters: obj []) : obj
Reflection.ConstructorInfo.Invoke(invokeAttr: Reflection.BindingFlags, binder: Reflection.Binder, parameters: obj [], culture: Globalization.CultureInfo) : obj
Reflection.MethodBase.Invoke(obj: obj, invokeAttr: Reflection.BindingFlags, binder: Reflection.Binder, parameters: obj [], culture: Globalization.CultureInfo) : obj
union case Option.None: Option<'T>
val this : Container
member Container.Register : lifetime:Lifetime -> unit

Full name: Script.Container.Register
val not : value:bool -> bool

Full name: Microsoft.FSharp.Core.Operators.not
val ctr : Constructor
val typeof<'T> : Type

Full name: Microsoft.FSharp.Core.Operators.typeof
Dictionary.Add(key: Type, value: Constructor * Lifetime) : unit
member Container.Register : unit -> unit

Full name: Script.Container.Register
member Container.Register : unit -> unit
member Container.Register : lifetime:Lifetime -> unit
member Container.Register : f:(unit -> 'TAbstract) -> unit (requires reference type)
member Container.Register : f:(unit -> 'TAbstract) * lifetime:Lifetime -> unit (requires reference type)
member Container.Register : f:(unit -> 'TAbstract) * lifetime:Lifetime -> unit (requires reference type)

Full name: Script.Container.Register
val f : (unit -> 'TAbstract) (requires reference type)
val box : value:'T -> obj

Full name: Microsoft.FSharp.Core.Operators.box
member Container.Register : f:(unit -> 'TAbstract) -> unit (requires reference type)

Full name: Script.Container.Register
member Container.Resolve : unit -> 'TAbstract (requires reference type)

Full name: Script.Container.Resolve
val sprintf : format:Printf.StringFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.sprintf
val invalidOp : message:string -> 'T

Full name: Microsoft.FSharp.Core.Operators.invalidOp
member Container.Release : instance:obj -> unit

Full name: Script.Container.Release
val instance : obj
module Seq

from Microsoft.FSharp.Collections
val filter : predicate:('T -> bool) -> source:seq<'T> -> seq<'T>

Full name: Microsoft.FSharp.Collections.Seq.filter
val pair : KeyValuePair<Type,obj>
property KeyValuePair.Value: obj
val toList : source:seq<'T> -> 'T list

Full name: Microsoft.FSharp.Collections.Seq.toList
Multiple items
type List<'T> =
  new : unit -> List<'T> + 2 overloads
  member Add : item:'T -> unit
  member AddRange : collection:IEnumerable<'T> -> unit
  member AsReadOnly : unit -> ReadOnlyCollection<'T>
  member BinarySearch : item:'T -> int + 2 overloads
  member Capacity : int with get, set
  member Clear : unit -> unit
  member Contains : item:'T -> bool
  member ConvertAll<'TOutput> : converter:Converter<'T, 'TOutput> -> List<'TOutput>
  member CopyTo : array:'T[] -> unit + 2 overloads
  ...
  nested type Enumerator

Full name: System.Collections.Generic.List<_>

--------------------
List() : unit
List(capacity: int) : unit
List(collection: IEnumerable<'T>) : unit
val iter : action:('T -> unit) -> list:'T list -> unit

Full name: Microsoft.FSharp.Collections.List.iter
Dictionary.Remove(key: Type) : bool
property KeyValuePair.Key: Type
val ignore : value:'T -> unit

Full name: Microsoft.FSharp.Core.Operators.ignore
module Usage

from Script
val container : Container

Full name: Script.Usage.container
type A =
  new : unit -> A

Full name: Script.Usage.A
val printfn : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
type B =
  new : unit -> B

Full name: Script.Usage.B
type C =
  new : a:A * b:B -> C

Full name: Script.Usage.C
val a : A
val b : B
new : unit -> B
val c : C

Full name: Script.Usage.c
member Container.Resolve : unit -> 'TAbstract (requires reference type)
val c' : C

Full name: Script.Usage.c'
member Container.Release : instance:obj -> unit
val c'' : C

Full name: Script.Usage.c''
type ICalculate =
  interface
    abstract member Incr : int -> int
  end

Full name: Script.Usage.ICalculate
abstract member ICalculate.Incr : int -> int

Full name: Script.Usage.ICalculate.Incr
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
Multiple items
type Calculator =
  interface ICalculate
  new : unit -> Calculator

Full name: Script.Usage.Calculator

--------------------
new : unit -> Calculator
val this : Calculator
override Calculator.Incr : x:int -> int

Full name: Script.Usage.Calculator.Incr
val x : int
val calc : ICalculate

Full name: Script.Usage.calc
abstract member ICalculate.Incr : int -> int
type Console =
  static member BackgroundColor : ConsoleColor with get, set
  static member Beep : unit -> unit + 1 overload
  static member BufferHeight : int with get, set
  static member BufferWidth : int with get, set
  static member CapsLock : bool
  static member Clear : unit -> unit
  static member CursorLeft : int with get, set
  static member CursorSize : int with get, set
  static member CursorTop : int with get, set
  static member CursorVisible : bool with get, set
  ...

Full name: System.Console
Console.ReadLine() : string
Next Version Raw view Test code New version

More information

Link:http://fssnip.net/9g
Posted:13 years ago
Author:Phillip Trelford
Tags: di , ioc , dependency injection , container