71 people like it.
Like the snippet!
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.
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
|
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
More information