thomaschrstnsn
thomaschrstnsn

Reputation: 51

Reflecting let bindings in a module by attribute and type

I am trying to find let bindings with a specific attribute and type, throughout a given assembly.

For instance, the following type and attribute:

type TargetType = { somedata: string }
type MarkingAttribute() = inherit System.Attribute()

Then I would like to find the value in the following module:

module SomeModule =
    [<Marking>]
    let valueIWantToFind = {somedata = "yoyo"} 

So what I am looking for is a function with the following signature (assuming it is suitable for a generic function signature):

let valuesOfTypeWithAttribute<'t,'attr> (assembly: Assembly) : 't list = ...

My futile attempts seem to be blocked by my lack of understanding how F# modules are translated to CLR (CLI?) classes.

I have the following FSI snippet which unfortunately finds nothing:

open System.Reflection
let types = Assembly.GetExecutingAssembly().GetTypes()

let fiWithAttribute (attributeType: System.Type) (fi: FieldInfo) =
    fi.CustomAttributes
    |> Seq.exists (fun attr -> attr.AttributeType = attributeType)

let fields = 
    types 
    |> Array.collect (fun t -> t.GetFields())
    |> Array.filter (fiWithAttribute typeof<MarkingAttribute>)

Any help or pointers will be greatly appreciated.

Upvotes: 3

Views: 258

Answers (2)

thomaschrstnsn
thomaschrstnsn

Reputation: 51

Mark's response led me onto the path of success. The reflection does not work for modules defined entirely in FSI (at least not for me in my setup).

The function I came up with looks like this:

open Microsoft.FSharp.Reflection
let letBindingsWithTypeAndAttribute<'t,'attr> (assembly: Assembly) : 't array =
    let publicTypes = assembly.GetExportedTypes ()
    let modules = publicTypes |> Array.filter FSharpType.IsModule
    let members = modules |> Array.collect (fun m -> m.GetMembers ())

    let miHasAttribute (mi : MemberInfo) =
        mi.GetCustomAttributes () 
        |> Seq.exists (fun attr' -> attr'.GetType() = typeof<'attr>)

    let withAttr = 
        members 
        |> Array.filter miHasAttribute

    let valueOfBinding (mi : MemberInfo) =
        let property = mi.Name
        mi.DeclaringType.GetProperty(property).GetValue null

    withAttr 
        |> Array.map valueOfBinding 
        |> Array.choose (fun o ->  match o with
                                    | :? 't as x -> Some x
                                    | _ -> None)

Upvotes: 2

Mark Seemann
Mark Seemann

Reputation: 233150

Modules are compiled as classes with static members. Load your assembly into a value called assembly, and start to investigate:

> let publicTypes = assembly.GetExportedTypes ();;

val publicTypes : System.Type [] =
  [|Ploeh.StackOverflow.Q36245870.TargetType;
    Ploeh.StackOverflow.Q36245870.MarkingAttribute;
    Ploeh.StackOverflow.Q36245870.SomeModule|]

As you can tell, SomeModule is one of those types:

> let someModule =
    publicTypes |> Array.find (fun t -> t.Name.EndsWith "SomeModule");;

val someModule : System.Type = Ploeh.StackOverflow.Q36245870.SomeModule

You can now get all members of the type:

> let members = someModule.GetMembers ();;

val members : MemberInfo [] =
  [|Ploeh.StackOverflow.Q36245870.TargetType get_valueIWantToFind();
    System.String ToString(); Boolean Equals(System.Object);
    Int32 GetHashCode(); System.Type GetType();
    Ploeh.StackOverflow.Q36245870.TargetType valueIWantToFind|]

This array includes the let-bound function valueIWantToFind, and it has the desired attribute:

> let attrs = members.[5].GetCustomAttributes ();;

val attrs : System.Collections.Generic.IEnumerable<System.Attribute> =
  [|Ploeh.StackOverflow.Q36245870.MarkingAttribute;
    Microsoft.FSharp.Core.CompilationMappingAttribute|]

Upvotes: 3

Related Questions