Click here to Skip to main content
Click here to Skip to main content

Tagged as

F#24 : Reflection

, 7 May 2014
Rate this:
Please Sign up or sign in to vote.
This time we will wrap up the OO leg of our F# journey, by looking into using reflection. Before we start, I just want to point out that some of the examples that I will use here are either modified versions of some examples, or are actual examples by the fabulous Chris Sells, from his […]

This time we will wrap up the OO leg of our F# journey, by looking into using reflection. Before we start, I just want to point out that some of the examples that I will use here are either modified versions of some examples, or are actual examples by the fabulous Chris Sells, from his amazing “Programming F# 3.0 book”, which I can thoroughly recommend. In the start of that book it states that you are free to use small portions of the code without permission, I considered what I have taken to be a very small portion.

Chris has quite nicely shared all the code from the book on his blog, and it contains some real gems, I would urge you to check it out.

General Reflection

NOTE : This is examples in this section and from Chris Sells blog, which I link to at the top of this post

As F# is a member of the .NET family, we are of course to use many of the standard reflection APIs that you have undoubtedly used in your day to day C# lives. However F# also provides extra functions/types and helpers that may be used to deal with F# types. We will look at this F# specific stuff later on within this post.

typeof vs typedefof

F# comes with 2 typexxof operators, typeof<…> and typedefof<..> which are subtly different

  • typeof<..> gives you a runtime representation of a static type
  • typedefof<..> gives a type definition of a static type.

To understand these differences lets see a small example:

printfn "typeof<List<int>> = %A" typeof<List<int>>
printfn "typedefof<List<int>> = %A" typedefof<List<int>>

Which when run will give the following results

image

Defining New Attributes

You have already some examples of attributes usage in F# in some of the previous posts we have done, for example you may have seen these in use

  • [<EntryPoint>]
  • [<AbstractClass>]

These obviously assist the F# compiler, but attributes can be used to do more than this, they can also provide meta data to your own application, and you can reflect on them at run time to see if a type supports certain things/contains certain meta data / control how object are serialized, there are in fact a lot of use cases for attributes. So it should come as no surprise that you should learn how to make your own attributes in F#.

Thankfully this is trivial, we will just build on our new formed OO knowledge and inherit from attribute, and create a new custom attribute.

/// Provide a description for a given class
[<AttributeUsage(AttributeTargets.Class)>]
type ClassDescriptionAttribute(desc) =
    inherit Attribute()
    member this.Description = desc

As you can see this is a new attribute which may only target classes, and will be used to describe a class.

Reflecting On Types

As previously stated, F# is a member of the .NET family so you are free to use any of the types/methods/properties within the standard CLR System.Reflection namespace. One thing you will likely have to do quit often is reflect on a particular type, so lets see an example of that:

// Prints the methods, properties, and fields of a type to the console
let describeType (ty : Type) =
    
    let bindingFlags = 
        BindingFlags.Public   ||| BindingFlags.NonPublic |||
        BindingFlags.Instance ||| BindingFlags.Static    |||
        BindingFlags.DeclaredOnly
    
    let methods = 
        ty.GetMethods(bindingFlags) 
        |> Array.fold (fun desc meth -> desc + sprintf "%s\r\n" meth.Name) ""
       
    let props = 
        ty.GetProperties(bindingFlags)
        |> Array.fold (fun desc prop -> desc + sprintf "%s\r\n" prop.Name) ""

    let fields =
        ty.GetFields(bindingFlags)
        |> Array.fold (fun desc field -> desc + sprintf "%s\r\n" field.Name) ""

    printfn "Name: %s" ty.Name
    printfn "Methods:    \n\t%s\n" methods
    printfn "Properties: \n\t%s\n" props
    printfn "Fields:     \n\t%s" fields

Which you can use like this

let s = new StringWriter()
do describeType(s.GetType())

Which when run gives the following results:

image

Inspecting Attributes

Inspecting attributes is also very easy (well thanks to Chris Sells blog that). Say we had these types:

/// Provide a description for a given class
[<AttributeUsage(AttributeTargets.Class)>]
type ClassDescriptionAttribute(desc) =
    inherit Attribute()
    member this.Description = desc

/// Provide a description for a given method
[<AttributeUsage(AttributeTargets.Method)>]
type MethodDescriptionAttribute(desc) =
    inherit Attribute()
    member this.Description = desc

type Widget = 
    | RedWidget 
    | GreenWidget
    | BlueWidget

/// XML Doc comments like this one are great for describing a class,
/// but are only available at compile-time. Metadata encoded into
/// attributes is available at run-time.
[<ClassDescription("Represents a stack of Widgets.")>]
type WidgetStack() =

    let mutable m_widgets : Widget list = []

    [<MethodDescription("Pushes a new Widget onto the stack.")>]
    member this.Push(x) = m_widgets <- x :: m_widgets

We can easily inspect the attributes and get their values using the following code (again thanks Chris Sells):

/// Displays data attributed with the MethodDesc or ClassDesc attributes
let printDocumentation(ty : Type) =

    // Return if a object has a given type
    let objHasType ty obj = (obj.GetType() = ty)

    let classDescription : string option = 
        ty.GetCustomAttributes(false)
        |> Seq.tryFind(objHasType typeof<ClassDescriptionAttribute>)
        |> Option.map(fun attr -> (attr :?> ClassDescriptionAttribute))
        |> Option.map(fun cda -> cda.Description)
    
    let methodDescriptions : seq<string * string option> =
        ty.GetMethods()
        |> Seq.map(fun mi -> mi, mi.GetCustomAttributes(false))
        |> Seq.map(fun (methodInfo, methodAttributes) ->
            let attributeDescription =
                methodAttributes
                |> Seq.tryFind(objHasType typeof<MethodDescriptionAttribute>)
                |> Option.map(fun atr -> (atr :?> MethodDescriptionAttribute))
                |> Option.map(fun mda -> mda.Description)
            methodInfo.Name, attributeDescription)
    
    let getDescription = function
                            | Some(desc) -> desc
                            | None       -> "(no description provided)"
    
    printfn "Info for class: %s" ty.Name
    printfn "Class Description:\n\t%s" (getDescription classDescription)
    printfn "Method Descriptions:"
    
    methodDescriptions 
    |> Seq.iter(fun (methName, desc) -> 
        printfn "\t%15s - %s" methName (getDescription desc))

Which when run with this demo code, will produce the output shown below

image

Properties, Get A Value, Set A Value

To get and set a property value is also trivial, we can just get a property using the standard System.Reflection namespace and then use the GetValue(..) and SetValue(..) methods exposed by the CLR.

I kind of feel a bit guilty using so many of Chris’s bit of code, but it is just so good, and highly enlightening. For example what is shown below, is very cool in that it shows how to create a new property get/set operator which behind the scenes uses reflection:

// Type for representing a book
type Book(title, author) =
    // Current page, if the book is opened
    let mutable m_currentPage : int option = None

    member this.Title  = title
    member this.Author = author
    member this.CurrentPage with get () = m_currentPage
                            and  set x  = m_currentPage <- x

    override this.ToString() =
        match m_currentPage with
        | Some(pg) -> sprintf "%s by %s, opened to page %d" title author pg
        | None     -> sprintf "%s by %s, not currently opened" title author

// CREATE SOME CUSTOM OPERATORS, WHICH IS AS EASY AS CREATING SOME FUNCTIONS
// IN F#

// Get a property value. Notice that the return type is generic.
let (?) (thingey : obj) (propName: string) : 'a =
    let propInfo = thingey.GetType().GetProperty(propName)
    propInfo.GetValue(thingey, null) :?> 'a

// Set a property value.
let (?<-) (thingey : obj) (propName : string) (newValue : 'a) =
    let propInfo = thingey.GetType().GetProperty(propName)
    propInfo.SetValue(thingey, newValue, null)

Which we can use as follows, take note of the “?” operators in action (which are really just functions, thus is the awesomeness of F#):

let book = new Book("Foundation", "Asimov")


printfn "%A" book.CurrentPage
book?CurrentPage <- Some(14)
printfn "%A" book.CurrentPage
let currentPage : int option = book?CurrentPage
printfn "%A" currentPage

book?CurrentPage <- Some(24)
printfn "%A" book.CurrentPage
let currentPage : int option = book?CurrentPage
printfn "%A" currentPage

Which when run gives us the following results:

image

F# Specific Reflection

What we have discussed so far has been how to leverage the existing .NET reflection namespace, which is totally fine, but what does F# have to offer us by way of reflection APIs.

F# actually offers a few modules to help with reflection of F# types. Lets have a brief look at these modules

Reflection.FSharpType

Contains operations associated with constructing and analyzing F# types such as records, unions and tuples. You can read more about this module here: http://msdn.microsoft.com/en-us/library/ee370530.aspx

Here is what is available within this module:

image

Here are some examples of this module

open Microsoft.FSharp.Reflection

type ContactCard = 
    {   Name     : string;
        Phone    : string; }

/// Represents the suit of a playing card
type Suit = 
    | Hearts 
    | Clubs 
    | Diamonds 
    | Spades

/// Represents the rank of a playing card
type Rank = 
    /// Represents the rank of cards 2 .. 10
    | Value of int
    | Ace
    | King
    | Queen
    | Jack
....
....
let aTuple = (12,13)
let aFunction() =
    printfn "Im function"
let aRecord = { Name = "Alf" ; Phone = "(206) 555-0157" ;  }
let aDiscrimatingUnion1 = Value(3)
let aDiscrimatingUnion2 = Rank.Ace



printfn "aTuple IsTuple = %A" (FSharpType.IsTuple(aTuple.GetType()))
printfn "aFunction IsTuple = %A" (FSharpType.IsTuple(aFunction.GetType()))

printfn "aTuple IsFunction = %A" (FSharpType.IsFunction(aTuple.GetType()))
printfn "aFunction IsFunction = %A" (FSharpType.IsFunction(aFunction.GetType()))

printfn "aRecord IsRecord = %A" (FSharpType.IsRecord(aRecord.GetType()))
 
printfn "aDiscrimatingUnion1 IsUnion = %A" (FSharpType.IsUnion(aDiscrimatingUnion1.GetType()))
printfn "aDiscrimatingUnion2 IsUnion = %A" (FSharpType.IsUnion(aDiscrimatingUnion2.GetType()))

Which when run will give the following output

image

Reflection.FSharpValue

Contains operations associated with constructing and analyzing values associated with F# types such as records, unions and tuples. You can read more about this module here: http://msdn.microsoft.com/en-us/library/ee353505.aspx

Here is what is available within this module:

image

Here are some examples of this module

open Microsoft.FSharp.Reflection
open System.Reflection

type ContactCard = 
    {   Name     : string;
        Phone    : string; }

/// Represents the suit of a playing card
type Suit = 
    | Hearts 
    | Clubs 
    | Diamonds 
    | Spades

/// Represents the rank of a playing card
type Rank = 
    /// Represents the rank of cards 2 .. 10
    | Value of int
    | Ace
    | King
    | Queen
    | Jack

....
....

let aRecord = { Name = "Alf" ; Phone = "(206) 555-0157" ;  }
let aDiscrimatingUnion1 = Value(3)
let aDiscrimatingUnion2 = Rank.Ace

printfn "GetRecordFields(aRecord, BindingFlags.Public) = %A" 
    (FSharpValue.GetRecordFields(aRecord, BindingFlags.Public))
printfn "GetUnionFields(aDiscrimatingUnion1, aDiscrimatingUnion2.GetType()) = %A" 
    (FSharpValue.GetUnionFields(aDiscrimatingUnion1, aDiscrimatingUnion2.GetType()))
printfn "GetUnionFields(aDiscrimatingUnion2, aDiscrimatingUnion2.GetType()) = %A" 
    (FSharpValue.GetUnionFields(aDiscrimatingUnion2, aDiscrimatingUnion2.GetType()))

Which when run gives the following results

image

Reflection.UnionCaseInfo

Represents a case of a discriminated union type. You can read more about this module here: http://msdn.microsoft.com/en-us/library/ee370473.aspx

Here is what is available within this module:

image

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Sacha Barber
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)
 
- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence
 
Both of these at Sussex University UK.
 
Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions

 
Question[My vote of 2] Code formatting PinmemberDGCom7-May-14 4:09 
AnswerRe: [My vote of 2] Code formatting PinmvpSacha Barber7-May-14 4:48 
GeneralRe: [My vote of 2] Code formatting PinmemberDGCom8-May-14 2:51 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140827.1 | Last Updated 7 May 2014
Article Copyright 2014 by Sacha Barber
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid