71 people like it.

Dynamic operator using Reflection

Demonstrates how to implement the dynamic operator (?) using .NET Reflection. The implementation supports calling constructors, propreties and methods using simple overload resolution (based on parameter count). It handles instance as well as static members.

Implementing dynamic operator

 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: 
open System
open System.Reflection
open Microsoft.FSharp.Reflection

// Various flags that specify what members can be called 
// NOTE: Remove 'BindingFlags.NonPublic' if you want a version
// that can call only public methods of classes
let private staticFlags = BindingFlags.NonPublic ||| BindingFlags.Public ||| BindingFlags.Static 
let private instanceFlags = BindingFlags.NonPublic ||| BindingFlags.Public ||| BindingFlags.Instance
let private ctorFlags = instanceFlags
let inline private asMethodBase(a:#MethodBase) = a :> MethodBase

// The operator takes just instance and a name. Depending on how it is used
// it either calls method (when 'R is function) or accesses a property
let (?) (o:obj) name : 'R =
  // The return type is a function, which means that we want to invoke a method
  if FSharpType.IsFunction(typeof<'R>) then

    // Get arguments (from a tuple) and their types
    let argType, resType = FSharpType.GetFunctionElements(typeof<'R>)
    // Construct an F# function as the result (and cast it to the
    // expected function type specified by 'R)
    FSharpValue.MakeFunction(typeof<'R>, fun args ->
      
      // We treat elements of a tuple passed as argument as a list of arguments
      // When the 'o' object is 'System.Type', we call static methods
      let methods, instance, args = 
        let args = 
          // If argument is unit, we treat it as no arguments,
          // if it is not a tuple, we create singleton array,
          // otherwise we get all elements of the tuple
          if argType = typeof<unit> then [| |]
          elif not(FSharpType.IsTuple(argType)) then [| args |]
          else FSharpValue.GetTupleFields(args)

        // Static member call (on value of type System.Type)?
        if (typeof<System.Type>).IsAssignableFrom(o.GetType()) then 
          let methods = (unbox<Type> o).GetMethods(staticFlags) |> Array.map asMethodBase
          let ctors = (unbox<Type> o).GetConstructors(ctorFlags) |> Array.map asMethodBase
          Array.concat [ methods; ctors ], null, args
        else 
          o.GetType().GetMethods(instanceFlags) |> Array.map asMethodBase, o, args
        
      // A simple overload resolution based on the name and the number of parameters only
      // TODO: This doesn't correctly handle multiple overloads with same parameter count
      let methods = 
        [ for m in methods do
            if m.Name = name && m.GetParameters().Length = args.Length then yield m ]
        
      // If we find suitable method or constructor to call, do it!
      match methods with 
      | [] -> failwithf "No method '%s' with %d arguments found" name args.Length
      | _::_::_ -> failwithf "Multiple methods '%s' with %d arguments found" name args.Length
      | [:? ConstructorInfo as c] -> c.Invoke(args)
      | [ m ] -> m.Invoke(instance, args) ) |> unbox<'R>

  else
    // The result type is not an F# function, so we're getting a property
    // When the 'o' object is 'System.Type', we access static properties
    let typ, flags, instance = 
      if (typeof<System.Type>).IsAssignableFrom(o.GetType()) 
        then unbox o, staticFlags, null
        else o.GetType(), instanceFlags, o
      
    // Find a property that we can call and get the value
    let prop = typ.GetProperty(name, flags)
    if prop = null && instance = null then 
      // The syntax can be also used to access nested types of a type
      let nested = typ.Assembly.GetType(typ.FullName + "+" + name)
      // Return nested type if we found one
      if nested = null then 
        failwithf "Property or nested type '%s' not found in '%s'." name typ.Name 
      elif not ((typeof<'R>).IsAssignableFrom(typeof<System.Type>)) then
        let rname = (typeof<'R>.Name)
        failwithf "Cannot return nested type '%s' as a type '%s'." nested.Name rname
      else nested |> box |> unbox<'R>
    else
      // Call property and return result if we found some
      let meth = prop.GetGetMethod(true)
      if prop = null then failwithf "Property '%s' found, but doesn't have 'get' method." name
      try meth.Invoke(instance, [| |]) |> unbox<'R>
      with _ -> failwithf "Failed to get value of '%s' property (of type '%s')" name typ.Name

Example of using the operator

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
// Create type that provides access to some types
type Mscorlib private () =
  static let asm = Assembly.Load("mscorlib")
  static member Random = asm.GetType("System.Random")
  static member Console = asm.GetType("System.Console")

// Dynamically invoke constructor with seed=1
let rnd : obj = Mscorlib.Random?``.ctor``(1)
// Invoke method without argument
let resf : float = rnd?NextDouble()
// Invoke method with argument
let resi : int = rnd?Next(10)

// Dynamically get value of a static property 
let bg : ConsoleColor = Mscorlib.Console?BackgroundColor
namespace System
namespace System.Reflection
namespace Microsoft
namespace Microsoft.FSharp
namespace Microsoft.FSharp.Reflection
val private staticFlags : BindingFlags

Full name: Script.staticFlags
type BindingFlags =
  | Default = 0
  | IgnoreCase = 1
  | DeclaredOnly = 2
  | Instance = 4
  | Static = 8
  | Public = 16
  | NonPublic = 32
  | FlattenHierarchy = 64
  | InvokeMethod = 256
  | CreateInstance = 512
  ...

Full name: System.Reflection.BindingFlags
field BindingFlags.NonPublic = 32
field BindingFlags.Public = 16
field BindingFlags.Static = 8
val private instanceFlags : BindingFlags

Full name: Script.instanceFlags
field BindingFlags.Instance = 4
val private ctorFlags : BindingFlags

Full name: Script.ctorFlags
val private asMethodBase : a:#MethodBase -> MethodBase

Full name: Script.asMethodBase
val a : #MethodBase
type MethodBase =
  inherit MemberInfo
  member Attributes : MethodAttributes
  member CallingConvention : CallingConventions
  member ContainsGenericParameters : bool
  member Equals : obj:obj -> bool
  member GetGenericArguments : unit -> Type[]
  member GetHashCode : unit -> int
  member GetMethodBody : unit -> MethodBody
  member GetMethodImplementationFlags : unit -> MethodImplAttributes
  member GetParameters : unit -> ParameterInfo[]
  member Invoke : obj:obj * parameters:obj[] -> obj + 1 overload
  ...

Full name: System.Reflection.MethodBase
val o : obj
type obj = Object

Full name: Microsoft.FSharp.Core.obj
val name : string
type FSharpType =
  static member GetExceptionFields : exceptionType:Type * ?bindingFlags:BindingFlags -> PropertyInfo []
  static member GetFunctionElements : functionType:Type -> Type * Type
  static member GetRecordFields : recordType:Type * ?bindingFlags:BindingFlags -> PropertyInfo []
  static member GetTupleElements : tupleType:Type -> Type []
  static member GetUnionCases : unionType:Type * ?bindingFlags:BindingFlags -> UnionCaseInfo []
  static member IsExceptionRepresentation : exceptionType:Type * ?bindingFlags:BindingFlags -> bool
  static member IsFunction : typ:Type -> bool
  static member IsModule : typ:Type -> bool
  static member IsRecord : typ:Type * ?bindingFlags:BindingFlags -> bool
  static member IsTuple : typ:Type -> bool
  ...

Full name: Microsoft.FSharp.Reflection.FSharpType
static member FSharpType.IsFunction : typ:Type -> bool
val typeof<'T> : Type

Full name: Microsoft.FSharp.Core.Operators.typeof
val argType : Type
val resType : Type
static member FSharpType.GetFunctionElements : functionType:Type -> Type * Type
type FSharpValue =
  static member GetExceptionFields : exn:obj * ?bindingFlags:BindingFlags -> obj []
  static member GetRecordField : record:obj * info:PropertyInfo -> obj
  static member GetRecordFields : record:obj * ?bindingFlags:BindingFlags -> obj []
  static member GetTupleField : tuple:obj * index:int -> obj
  static member GetTupleFields : tuple:obj -> obj []
  static member GetUnionFields : value:obj * unionType:Type * ?bindingFlags:BindingFlags -> UnionCaseInfo * obj []
  static member MakeFunction : functionType:Type * implementation:(obj -> obj) -> obj
  static member MakeRecord : recordType:Type * values:obj [] * ?bindingFlags:BindingFlags -> obj
  static member MakeTuple : tupleElements:obj [] * tupleType:Type -> obj
  static member MakeUnion : unionCase:UnionCaseInfo * args:obj [] * ?bindingFlags:BindingFlags -> obj
  ...

Full name: Microsoft.FSharp.Reflection.FSharpValue
static member FSharpValue.MakeFunction : functionType:Type * implementation:(obj -> obj) -> obj
val args : obj
val methods : MethodBase []
val instance : obj
val args : obj []
type unit = Unit

Full name: Microsoft.FSharp.Core.unit
val not : value:bool -> bool

Full name: Microsoft.FSharp.Core.Operators.not
static member FSharpType.IsTuple : typ:Type -> bool
static member FSharpValue.GetTupleFields : tuple:obj -> obj []
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
Object.GetType() : Type
val unbox : value:obj -> 'T

Full name: Microsoft.FSharp.Core.Operators.unbox
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 map : mapping:('T -> 'U) -> array:'T [] -> 'U []

Full name: Microsoft.FSharp.Collections.Array.map
val ctors : MethodBase []
val concat : arrays:seq<'T []> -> 'T []

Full name: Microsoft.FSharp.Collections.Array.concat
val methods : MethodBase list
val m : MethodBase
property MemberInfo.Name: string
MethodBase.GetParameters() : ParameterInfo []
property Array.Length: int
val failwithf : format:Printf.StringFormat<'T,'Result> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.failwithf
type ConstructorInfo =
  inherit MethodBase
  member Equals : obj:obj -> bool
  member GetHashCode : unit -> int
  member Invoke : parameters:obj[] -> obj + 1 overload
  member MemberType : MemberTypes
  static val ConstructorName : string
  static val TypeConstructorName : string

Full name: System.Reflection.ConstructorInfo
val c : ConstructorInfo
ConstructorInfo.Invoke(parameters: obj []) : obj
MethodBase.Invoke(obj: obj, parameters: obj []) : obj
ConstructorInfo.Invoke(invokeAttr: BindingFlags, binder: Binder, parameters: obj [], culture: Globalization.CultureInfo) : obj
MethodBase.Invoke(obj: obj, invokeAttr: BindingFlags, binder: Binder, parameters: obj [], culture: Globalization.CultureInfo) : obj
MethodBase.Invoke(obj: obj, parameters: obj []) : obj
MethodBase.Invoke(obj: obj, invokeAttr: BindingFlags, binder: Binder, parameters: obj [], culture: Globalization.CultureInfo) : obj
val typ : Type
val flags : BindingFlags
val prop : PropertyInfo
Type.GetProperty(name: string) : PropertyInfo
Type.GetProperty(name: string, returnType: Type) : PropertyInfo
Type.GetProperty(name: string, types: Type []) : PropertyInfo
Type.GetProperty(name: string, bindingAttr: BindingFlags) : PropertyInfo
Type.GetProperty(name: string, returnType: Type, types: Type []) : PropertyInfo
Type.GetProperty(name: string, returnType: Type, types: Type [], modifiers: ParameterModifier []) : PropertyInfo
Type.GetProperty(name: string, bindingAttr: BindingFlags, binder: Binder, returnType: Type, types: Type [], modifiers: ParameterModifier []) : PropertyInfo
val nested : Type
property Type.Assembly: Assembly
Object.GetType() : Type
Assembly.GetType(name: string) : Type
Assembly.GetType(name: string, throwOnError: bool) : Type
Assembly.GetType(name: string, throwOnError: bool, ignoreCase: bool) : Type
property Type.FullName: string
val rname : string
val box : value:'T -> obj

Full name: Microsoft.FSharp.Core.Operators.box
val meth : MethodInfo
PropertyInfo.GetGetMethod() : MethodInfo
PropertyInfo.GetGetMethod(nonPublic: bool) : MethodInfo
Multiple items
type Mscorlib =
  private new : unit -> Mscorlib
  static member Console : Type
  static member Random : Type

Full name: Script.Mscorlib

--------------------
private new : unit -> Mscorlib
val asm : Assembly
type Assembly =
  member CodeBase : string
  member CreateInstance : typeName:string -> obj + 2 overloads
  member EntryPoint : MethodInfo
  member Equals : o:obj -> bool
  member EscapedCodeBase : string
  member Evidence : Evidence
  member FullName : string
  member GetCustomAttributes : inherit:bool -> obj[] + 1 overload
  member GetCustomAttributesData : unit -> IList<CustomAttributeData>
  member GetExportedTypes : unit -> Type[]
  ...

Full name: System.Reflection.Assembly
Assembly.Load(rawAssembly: byte []) : Assembly
Assembly.Load(assemblyRef: AssemblyName) : Assembly
Assembly.Load(assemblyString: string) : Assembly
Assembly.Load(rawAssembly: byte [], rawSymbolStore: byte []) : Assembly
Assembly.Load(rawAssembly: byte [], rawSymbolStore: byte [], securityContextSource: Security.SecurityContextSource) : Assembly
Multiple items
static member Mscorlib.Random : Type

Full name: Script.Mscorlib.Random

--------------------
type Random =
  new : unit -> Random + 1 overload
  member Next : unit -> int + 2 overloads
  member NextBytes : buffer:byte[] -> unit
  member NextDouble : unit -> float

Full name: System.Random

--------------------
Random() : unit
Random(Seed: int) : unit
Multiple items
static member Mscorlib.Console : Type

Full name: Script.Mscorlib.Console

--------------------
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
val rnd : obj

Full name: Script.rnd
type Mscorlib =
  private new : unit -> Mscorlib
  static member Console : Type
  static member Random : Type

Full name: Script.Mscorlib
property Mscorlib.Random: Type
val resf : float

Full name: Script.resf
Multiple items
val float : value:'T -> float (requires member op_Explicit)

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

--------------------
type float = Double

Full name: Microsoft.FSharp.Core.float

--------------------
type float<'Measure> = float

Full name: Microsoft.FSharp.Core.float<_>
val resi : int

Full name: Script.resi
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<_>
val bg : ConsoleColor

Full name: Script.bg
type ConsoleColor =
  | Black = 0
  | DarkBlue = 1
  | DarkGreen = 2
  | DarkCyan = 3
  | DarkRed = 4
  | DarkMagenta = 5
  | DarkYellow = 6
  | Gray = 7
  | DarkGray = 8
  | Blue = 9
  ...

Full name: System.ConsoleColor
property Mscorlib.Console: Type
Next Version Raw view Test code New version

More information

Link:http://fssnip.net/2V
Posted:13 years ago
Author:Tomas Petricek
Tags: dynamic , invoke , reflection