F# infoof operator (part 4)
With propertyof, methodof, methoddefof, and eventof covered in the preceding posts, we can now assemble the complete MemberInfo module. Three helpers remain: fieldof, whose implementation closely follows propertyof; constructorof, which returns a System.Reflection.ConstructorInfo from an object creation expression; and unioncaseof, which returns a Microsoft.FSharp.Reflection.UnionCaseInfo from a union case construction or test expression. Their implementations are straightforward and are included in the full module below.
Module signature:
module MemberInfo
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Reflection
open System.Reflection
val fieldof : Expr -> FieldInfo
val propertyof : Expr -> PropertyInfo
val methodof : Expr -> MethodInfo
val methoddefof : Expr -> MethodInfo
val constructorof : Expr -> ConstructorInfo
val unioncaseof : Expr -> UnionCaseInfo
val eventof : Expr -> EventInfo
Module implementation:
module MemberInfo
open Microsoft.FSharp.Quotations.Patterns
open Microsoft.FSharp.Quotations.DerivedPatterns
open System.Reflection
/// Return FieldInfo for a field access expression.
let fieldof expr =
match expr with
| FieldGet(_, info) -> info
| Lambda(arg, FieldGet(Some(Var var), info))
| Let(arg, _, FieldGet(Some(Var var), info))
when arg = var -> info
| _ -> failwith "Not a field expression"
/// Return PropertyInfo for a property access expression.
let propertyof expr =
match expr with
| PropertyGet(_, info, _) -> info
| Lambda(arg, PropertyGet(Some(Var var), info, _))
| Let(arg, _, PropertyGet(Some(Var var), info, _))
when arg = var -> info
| _ -> failwith "Not a property expression"
/// Match a function value generated by F#
/// from a method or function.
let private (|Func|_|) expr =
let onlyVar = function Var v -> Some v | _ -> None
match expr with
// function values for methods with no parameters
| Lambda(arg, Call(target, info, []))
when arg.Type = typeof<unit> -> Some(target, info)
// function values with one argument
| Lambda(arg, Call(target, info, [ Var var ]))
when arg = var -> Some(target, info)
// function values with curried
// or tupled arguments
| Lambdas(args, Call(target, info, exprs))
when List.choose onlyVar exprs
= List.concat args -> Some(target, info)
| _ -> None
/// Return MethodInfo for a method call
/// or a function value expression.
let methodof expr =
match expr with
// ordinary calls: foo.Bar()
| Call(_, info, _) -> info
// calls and function values through a lambda parameter:
// fun (x: string) -> x.Substring(1, 2)
// fun (x: string) -> x.StartsWith
| Lambda(arg, Call(Some(Var var), info, _))
| Lambda(arg, Func(Some(Var var), info))
when arg = var -> info
// function values:
// someString.StartsWith
| Func(_, info) -> info
// calls and function values through instance expressions:
// "abc".StartsWith("a")
// "abc".Substring
| Let(arg, _, Call(Some (Var var), info, _))
| Let(arg, _, Func(Some (Var var), info))
when arg = var -> info
| _ -> failwith "Not a method expression"
/// Return a generic method definition from
/// a generic method call or
/// a function value expression.
let methoddefof expr =
match methodof expr with
| info when info.IsGenericMethod ->
info.GetGenericMethodDefinition()
| info -> failwithf "%A is not generic" info
/// Return ConstructorInfo for an expression
/// creating an object or an F# record.
let constructorof expr =
match expr with
| NewObject(info, _) -> info
// get the record constructor
| NewRecord(recordType, _) ->
match recordType.GetConstructors() with
| [| info |] -> info
| _ -> failwith "Invalid record type"
| _ -> failwith "Not a constructor expression"
/// Return EventInfo for an expression
/// accessing a CLI-compatible event.
let eventof expr =
match expr with
| Call(None, createEvent, [
Lambda(arg1, Call(_, addHandler, [ Var var1 ]))
Lambda(arg2, Call(_, removeHandler, [ Var var2 ]))
Lambda(_, NewDelegate _)
])
when createEvent.Name = "CreateEvent"
&& addHandler.Name.StartsWith("add_")
&& removeHandler.Name.StartsWith("remove_")
&& arg1 = var1
&& arg2 = var2 ->
addHandler.DeclaringType.GetEvent(
addHandler.Name.Remove(0, 4),
BindingFlags.Public ||| BindingFlags.Instance |||
BindingFlags.Static ||| BindingFlags.NonPublic)
| _ -> failwith "Not an event expression"
/// Return UnionCaseInfo for a union case
/// construction or test expression.
let unioncaseof expr =
match expr with
| NewUnionCase(info, _)
| UnionCaseTest(_, info) -> info
| _ -> failwith "Not a union case expression"