Home > other >  How can I make this reflection code work for any specific case of a generic type?
How can I make this reflection code work for any specific case of a generic type?

Time:09-30

I have some data coming from some system via a TcpClient.

The system is a kind of column store with it's own way of representing null values. The system supports null for any type including int, DateTime etc.

The TcpClient returns values like DateTime.MinValue in case of nulls. But the client also has a null checking utility.

I have put together some reflection code to generate types, which works fine as long as I don't have any option types. I want to adapt this code to map null values to Option<'t>.

So far, I have the following utility.

let checkNull (x:obj) =
    let elementType = x.GetType()
    let defType = typedefof<ValueOption<_>>;
    let genericType = defType.MakeGenericType elementType
    let optionCaseInfoSome, _ = FSharpValue.GetUnionFields(DateTime.Now |> ValueSome :> obj, genericType)
    let valueNone:ValueOption<DateTime> = ValueNone
    let optionCaseInfoNone, _ = FSharpValue.GetUnionFields(valueNone :> obj, genericType)
    if KdbConstants.IsNull x then
        FSharpValue.MakeUnion(optionCaseInfoNone, Array.empty)
    else
        FSharpValue.MakeUnion(optionCaseInfoSome, [|x|])

This will only work for Option<DateTime> values. How can I adapt this to work for any Option<t>`?

PS: I am aware that my utility is not efficient and much of the values can be pre-calculated, rather than being run each time the function is called. I will clean it up once I get it to work.

CodePudding user response:

I think I have an answer to this

let makeOptionCaseMetaData (x:obj) =
    let elementType = x.GetType()
    let defType = typedefof<Option<_>>;
    let genericType = defType.MakeGenericType elementType
    let optionCaseInfoNone, optionCaseInfoSome = FSharpType.GetUnionCases(genericType) |> (fun a -> a[0], a[1])
    {OptionCaseInfoSome = optionCaseInfoSome; OptionCaseInfoNone = optionCaseInfoNone}

let checkNull (x:obj) =
    let metaData = makeOptionCaseMetaData x
    if KdbConstants.IsNull x then
        FSharpValue.MakeUnion(metaData.OptionCaseInfoNone, Array.empty)
    else
        FSharpValue.MakeUnion(metaData.OptionCaseInfoSome, [|x|])

Still not very efficient but I can worry about that separately.

CodePudding user response:

I think you can simplify this and avoid using some of the reflection. You can define a normal generic method to do the conversion:

// For illustration, turn "" to `null`
let isNull (x:obj) = unbox x = "" 

// Static method (easier to access via reflection) that does the null check
type OptionConverter =
  static member CheckNull<'T>(x:'T) = 
    if isNull x then None
    else Some x

If you now get some object, you can create a generic instantiation of CheckNull and invoke the method to do the work:

let checkNull (x:obj) =
  let checkMi = typeof<OptionConverter>.GetMethod("CheckNull")
  checkMi.MakeGenericMethod(x.GetType()).Invoke(null, [| x |])

checkNull ""    // None (shows as `<null>` in FSI because it is of type `obj`)
checkNull "Yo"  // Some "Yo"
  • Related