F Sharp Programming/Reflection

From Wikibooks, open books for an open world
Jump to navigation Jump to search
Previous: Advanced Data Structures Index Next: Quotations
F# : Reflection

Reflection allows programmers to inspect types and invoke methods of objects at runtime without knowing their data type at compile time.

At first glance, reflection seems to go against the spirit of ML as it is inherently not type-safe, so typing errors using reflection are not discovered until runtime. However, .NET's typing philosophy is best stated as static typing where possible, dynamic typing when needed, where reflection serves to bring in the most desirable behaviors of dynamic typing into the static typing world. In fact, dynamic typing can be a huge time saver, often promotes the design of more expressive APIs, and allows code to be refactored much further than possible with static typing.

This section is intended as a cursory overview of reflection, not a comprehensive tutorial.

Inspecting Types[edit | edit source]

There are a variety of ways to inspect the type of an object. The most direct way is calling the .GetType() method (inherited from System.Object) on any non-null object:

> "hello world".GetType();;
val it : System.Type =
  System.String
    {Assembly = mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;
     AssemblyQualifiedName = "System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
     Attributes = AutoLayout, AnsiClass, Class, Public, Sealed, Serializable, BeforeFieldInit;
     BaseType = System.Object;
     ContainsGenericParameters = false;
     DeclaringMethod = ?;
     DeclaringType = null;
     FullName = "System.String";
     GUID = 296afbff-1b0b-3ff5-9d6c-4e7e599f8b57;
     GenericParameterAttributes = ?;
     GenericParameterPosition = ?;
     ...

Its also possible to get type information without an actual object using the built-in typeof method:

> typeof<System.IO.File>;;
val it : System.Type =
  System.IO.File
    {Assembly = mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;
     AssemblyQualifiedName = "System.IO.File, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
     Attributes = AutoLayout, AnsiClass, Class, Public, Abstract, Sealed, BeforeFieldInit;
     BaseType = System.Object;
     ContainsGenericParameters = false;
     DeclaringMethod = ?;
     DeclaringType = null;
     FullName = "System.IO.File";
     ...

object.GetType and typeof return an instance of System.Type, which has a variety of useful properties such as:

  • val Name : string
Returns the name of the type.
  • val GetConstructors : unit -> ConstructorInfo array
Returns an array of constructors defined on the type.
  • val GetMembers : unit -> MemberInfo array
Returns an array of members defined on the type.
  • val InvokeMember : (name : string, invokeAttr : BindingFlags, binder : Binder, target : obj, args : obj) -> obj
Invokes the specified member, using the specified binding constraints and matching the specified argument list

Example: Reading Properties[edit | edit source]

The following program will print out the properties of any object passed into it:

type Car(make : string, model : string, year : int) =
    member this.Make = make
    member this.Model = model
    member this.Year = year
    member this.WheelCount = 4
    
type Cat() =
    let mutable age = 3
    let mutable name = System.String.Empty
    
    member this.Purr() = printfn "Purrr"
    member this.Age
        with get() = age
        and set(v) = age <- v
    member this.Name
        with get() = name
        and set(v) = name <- v
        
let printProperties x =
    let t = x.GetType()
    let properties = t.GetProperties()
    printfn "-----------"
    printfn "%s" t.FullName
    properties |> Array.iter (fun prop ->
        if prop.CanRead then
            let value = prop.GetValue(x, null)
            printfn "%s: %O" prop.Name value
        else
            printfn "%s: ?" prop.Name)

let carInstance = new Car("Ford", "Focus", 2009)
let catInstance =
    let temp = new Cat()
    temp.Name <- "Mittens"
    temp
    
printProperties carInstance
printProperties catInstance

This program outputs the following:

-----------
Program+Car
WheelCount: 4
Year: 2009
Model: Focus
Make: Ford
-----------
Program+Cat
Name: Mittens
Age: 3

Example: Setting Private Fields[edit | edit source]

In addition to discovering types, we can dynamically invoke methods and set properties:

let dynamicSet x propName propValue =
    let property = x.GetType().GetProperty(propName)
    property.SetValue(x, propValue, null)

Reflection is particularly remarkable in that it can read/write private fields, even on objects which appear to be immutable. In particular, we can explore and manipulate the underlying properties of an F# list:

> open System.Reflection
let x = [1;2;3;4;5]
let lastNode = x.Tail.Tail.Tail.Tail;;

val x : int list = [1; 2; 3; 4; 5]
val lastNode : int list = [5]

> lastNode.GetType().GetFields(BindingFlags.NonPublic ||| BindingFlags.Instance) |> Array.map (fun field -> field.Name);;
val it : string array = [|"__Head"; "__Tail"|]
> let tailField = lastNode.GetType().GetField("__Tail", BindingFlags.NonPublic ||| BindingFlags.Instance);;

val tailField : FieldInfo =
  Microsoft.FSharp.Collections.FSharpList`1[System.Int32] __Tail

> tailField.SetValue(lastNode, x);; (* circular list *)
val it : unit = ()
> x |> Seq.take 20 |> Seq.to_list;;
val it : int list =
  [1; 2; 3; 4; 5; 1; 2; 3; 4; 5; 1; 2; 3; 4; 5; 1; 2; 3; 4; 5]

The example above mutates the list in place and to produce a circularly linked list. In .NET, "immutable" doesn't really mean immutable and private members are mostly an illusion.

Note: The power of reflection has definite security implications, but a full discussion of reflection security is far outside of the scope of this section. Readers are encouraged to visit the Security Considerations for Reflection article on MSDN for more information.

Microsoft.FSharp.Reflection Namespace[edit | edit source]

While .NET's built-in reflection API is useful, the F# compiler performs a lot of magic which makes built-in types like unions, tuples, functions, and other built-in types appear strange using vanilla reflection. The Microsoft.FSharp.Reflection namespace provides a wrapper for exploring F# types.

open System.Reflection
open Microsoft.FSharp.Reflection

let explore x =
    let t = x.GetType()
    if FSharpType.IsTuple(t) then
        let fields =
            FSharpValue.GetTupleFields(x)
            |> Array.map string
            |> fun strings -> System.String.Join(", ", strings)
        
        printfn "Tuple: (%s)" fields
    elif FSharpType.IsUnion(t) then
        let union, fields =  FSharpValue.GetUnionFields(x, t)
        
        printfn "Union: %s(%A)" union.Name fields
    else
        printfn "Got another type"

Using fsi:

> explore (Some("Hello world"));;
Union: Some([|"Hello world"|])
val it : unit = ()

> explore (7, "Hello world");;
Tuple: (7, Hello world)
val it : unit = ()

> explore (Some("Hello world"));;
Union: Some([|"Hello world"|])
val it : unit = ()

> explore [1;2;3;4];;
Union: Cons([|1; [2; 3; 4]|])
val it : unit = ()

> explore "Hello world";;
Got another type

Working With Attributes[edit | edit source]

.NET attributes and reflection go hand-in-hand. Attributes allow programmers to decorate classes, methods, members, and other source code with metadata used at runtime. Many .NET classes use attributes to annotate code in a variety of ways; it is only possible to access and interpret attributes through reflection. This section will provide a brief overview of attributes. Readers interested in a more complete overview are encouraged to read MSDN's Extending Metadata With Attributes series.

Attributes are defined using [<AttributeName>], a notation already seen in a variety of places in previous chapters of this book. The .NET framework includes a number of built-in attributes, including:

We can create custom attributes by defining a new type which inherits from System.Attribute:

type MyAttribute(text : string) =
    inherit System.Attribute()
    
    do printfn "MyAttribute created. Text: %s" text
    
    member this.Text = text

[<MyAttribute("Hello world")>]    
type MyClass() =
    member this.SomeProperty = "This is a property"

We can access attribute using reflection:

> let x = new MyClass();;

val x : MyClass

> x.GetType().GetCustomAttributes(true);;
MyAttribute created. Text: Hello world
val it : obj [] =
  [|System.SerializableAttribute {TypeId = System.SerializableAttribute;};
    FSI_0028+MyAttribute {Text = "Hello world";
                          TypeId = FSI_0028+MyAttribute;};
    Microsoft.FSharp.Core.CompilationMappingAttribute
      {SequenceNumber = 0;
       SourceConstructFlags = ObjectType;
       TypeId = Microsoft.FSharp.Core.CompilationMappingAttribute;
       VariantNumber = 0;}|]

The MyAttribute class has the side-effect of printing to the console on instantiation, demonstrating that MyAttribute does not get constructed when instances of MyClass are created.

Example: Encapsulating Singleton Design Pattern[edit | edit source]

Attributes are often used to decorate classes with any kind of ad-hoc functionality. For example, let's say we wanted to control whether single or multiple instances of classes are created based on an attribute:

open System
open System.Collections.Generic

[<AttributeUsage(AttributeTargets.Class)>]
type ConstructionAttribute(singleInstance : bool) =
    inherit Attribute()
    member this.IsSingleton = singleInstance

let singletons = Dictionary<System.Type,obj>()
let make<'a>() : 'a =
    let newInstance() = Activator.CreateInstance<'a>()
    let attributes = typeof<'a>.GetCustomAttributes(typeof<ConstructionAttribute>, true)
    let singleInstance =
        if attributes.Length > 0 then
            let constructionAttribute = attributes.[0] :?> ConstructionAttribute
            constructionAttribute.IsSingleton
        else false
    
    if singleInstance then
        match singletons.TryGetValue(typeof<'a>) with
        | true, v -> v :?> 'a
        | _ ->
            let instance = newInstance()
            singletons.Add(typeof<'a>, instance)
            instance
    else newInstance()

[<ConstructionAttribute(true)>]
type SingleOnly() =
    do printfn "SingleOnly constructor"

[<ConstructionAttribute(false)>]
type NewAlways() =
    do printfn "NewAlways constructor"

let x = make<SingleOnly>()
let x' = make<SingleOnly>()
let y = make<NewAlways>()
let y' = make<NewAlways>()

printfn "x = x': %b" (x = x')
printfn "y = y': %b" (y = y')
Console.ReadKey(true) |> ignore

This program outputs the following:

SingleOnly constructor
NewAlways constructor
NewAlways constructor
x = x': true
y = y': false

Using the attribute above, we've completely abstracted away the implementation details of the singleton design pattern, reducing it down to a single attribute. Its worth noting that the program above hard-codes a value of true or false into the attribute constructor; if we wanted to, we could pass a string representing a key from the application's config file and make class construction dependent on the config file.

Previous: Advanced Data Structures Index Next: Quotations