F# Dynamic and DynamicAssignment operators are not as simple as they appear ex facte. Most popular sample of their usage is something like this:
let (?) o n : 'T =
let prop = o.GetType().GetProperty(n)
downcast prop.GetValue(o, null)
let (?<-) o n v =
let prop = o.GetType().GetProperty(n)
prop.SetValue(o, box v, null)
let sb = new System.Text.StringBuilder()
sb?Length <- 20
let l : int = sb?Length // 20
Everything is clear...but what if the exact property name is unknown in the invocation point.
let (?) o n : 'T =
let prop = o.GetType().GetProperty(n)
downcast prop.GetValue(o, null)
let (?<-) o n v =
let prop = o.GetType().GetProperty(n)
prop.SetValue(o, box v, null)
let setAndGetProperty s name value =
s?name <- value
printfn "%A" s?name
let sb = new System.Text.StringBuilder()
setAndGetProperty sb "Length" 20
(*
System.NullReferenceException: Object reference not set to an instance of an object.
at FSI_0013.op_DynamicAssignment[a,b](a o, String n, b v) in C:\Users\vladm\AppData\Local\Temp\~vs8CA8.fsx:line 15
at FSI_0013.setAndGetProperty[a,b,c](a s, b name, c value) in C:\Users\vladm\AppData\Local\Temp\~vs8CA8.fsx:line 18
at <StartupCode$FSI_0013>.$FSI_0013.main@() in C:\Users\vladm\AppData\Local\Temp\~vs8CA8.fsx:line 22
Stopped due to error
*)
Quite unexpected huh?!. Compiler translates expr ? identifier into (?) expr "identifier" and in case of indirect references n in ? will be equal to "name" not to "Length". To prove it let's add some tracing:
let (?) o n : 'T =
printfn "Requested property name for get'%s'" n
let prop = o.GetType().GetProperty(n)
downcast prop.GetValue(o, null)
let (?<-) o n v =
printfn "Requested property name for set '%s'" n
let prop = o.GetType().GetProperty(n)
prop.SetValue(o, box v, null)
let setAndGetProperty s name value =
s?name <- value
printfn "%A" s?name
let sb = new System.Text.StringBuilder()
setAndGetProperty sb "Length" 20
(*
Requested property name for set 'name'
System.NullReferenceException: Object reference not set to an instance of an object.
at FSI_0015.op_DynamicAssignment[a,b](a o, String n, b v) in C:\Users\vladm\AppData\Local\Temp\~vs8CA8.fsx:line 17
at FSI_0015.setAndGetProperty[a,b,c](a s, b name, c value) in C:\Users\vladm\AppData\Local\Temp\~vs8CA8.fsx:line 20
at <StartupCode$FSI_0015>.$FSI_0015.main@() in C:\Users\vladm\AppData\Local\Temp\~vs8CA8.fsx:line 24
Stopped due to error
*)
Fix is very simple: change ?id to ?(id). It will change translation rules to : expr?identifier -> (?) expr identifier.
let (?) o n : 'T =
printfn "Requested property name for get'%s'" n
let prop = o.GetType().GetProperty(n)
downcast prop.GetValue(o, null)
let (?<-) o n v =
printfn "Requested property name for set '%s'" n
let prop = o.GetType().GetProperty(n)
prop.SetValue(o, box v, null)
let setAndGetProperty s name value =
s?(name) <- value
printfn "%A" s?(name)
let sb = new System.Text.StringBuilder()
setAndGetProperty sb "Length" 20
(*
Requested property name for set 'Length'
Requested property name for get'Length'
20
*)