mercredi 11 décembre 2019

F# Reflection : Passing an Array as Argument to MethodInfo.Invoke

In a previous post I was shown how to use F# Reflection to get keys from a single boxed Map object, Map<'k,'v>, at runtime. I tried to extend the idea and pass an array of boxed Map objects, Map<'k,'v>[], but I cannot find a way to extend the original single-object approach to take an array of objects as argument. I came up with a solution which works but doesn't look right. I am looking for a better, more idiomatic way.

In the code below, keysFromMap gets an array of keys from a single boxed Map<'k,'v> argument; keysFromMapArray is my first attempt to do the same from a boxed Map<'k,'v>[] argument - but it does not work - and keysFromMapArrayWithCast does work, but having to do the downcast at the FromMapArrayWithCast getter level does not seem right.

The error message I get from running keysFromMapArray (test2 commented out) :

{System.ArgumentException: Object of type 'System.Object[]' cannot be converted to type 'Microsoft.FSharp.Collections.FSharpMap`2[System.String,System.Int32][]'.

My question : why extending the keysFromMap approach to take an array of Maps argument does not work, and how to fix it so that it does?

Example code:

module Example = 
    open System

    let foo1 = [| ("foo11", 11); ("foo12", 12) |] |> Map.ofArray
    let foo2 = [| ("foo21", 21); ("foo22", 22) |] |> Map.ofArray

    type KeyGetter = 
        static member FromMap<'K, 'V when 'K : comparison>(map:Map<'K, 'V>) = 
            [| for kvp in map -> kvp.Key |]

        static member FromMapArray<'K, 'V when 'K : comparison>(maps:Map<'K, 'V>[]) = 
            maps |> Array.map (fun mp -> [| for kvp in mp -> kvp.Key |]) |> Array.concat

        static member FromMapArrayWithCast<'K, 'V when 'K : comparison>(omaps:obj[]) = 
            let typedmaps = [| for omp in omaps -> omp :?> Map<'K, 'V> |]  // -- DOWNCASTING HERE --
            typedmaps |> Array.map (fun mp -> [| for kvp in mp -> kvp.Key |]) |> Array.concat

    let keysFromMap (oMap : obj) : obj[] =
        let otype = oMap.GetType()    
        match otype.Name with
        | "FSharpMap`2" ->          
            typeof<KeyGetter>.GetMethod("FromMap")
                .MakeGenericMethod(otype.GetGenericArguments())
                .Invoke(null, [| box oMap |]) :?> obj[]
        | _             ->  
            Array.empty

    let keysFromMapArray (oMaps : obj[]) : obj[] =
        // skipped : tests to check that oMaps is not empty, and that all elements have the same type...
        let otype = oMaps.[0].GetType()    
        match otype.Name with
        | "FSharpMap`2" ->          

            typeof<KeyGetter>.GetMethod("FromMapArray")
                .MakeGenericMethod(otype.GetGenericArguments())
                .Invoke(null, [| box oMaps |]) :?> obj[] // -- FAILS HERE --
        | _             ->  
            Array.empty

    let keysFromMapArrayWithCast (oMaps : obj[]) : obj[] =
        // skipped : tests to check that oMaps is not empty, and that all elements have the same type...
        let otype = oMaps.[0].GetType()    
        match otype.Name with
        | "FSharpMap`2" ->          

            typeof<KeyGetter>.GetMethod("FromMapArrayWithCast")
                .MakeGenericMethod(otype.GetGenericArguments())
                .Invoke(null, [| box oMaps |]) :?> obj[]
        | _             ->  
            Array.empty

    [<EntryPoint>]
    let main argv =
        printfn "#test1: keys from Map<'k,'v> - works"
        let test = keysFromMap foo1

        // printfn "#test2: keysFromArray from Map<'k,'v>[] - FAILS"
        // let test = keysFromMapArray [| foo1; foo2 |]

        printfn "#test3: keysFromArrayWithCast from obj[] - works"
        let test = keysFromMapArrayWithCast [| foo1; foo2 |]

        Console.ReadKey() |> ignore
        0 // return exit code 0




Aucun commentaire:

Enregistrer un commentaire