【问题标题】:F# Reflection : Passing an Array as Argument to MethodInfo.InvokeF# 反射:将数组作为参数传递给 MethodInfo.Invoke
【发布时间】:2019-12-11 07:44:18
【问题描述】:

previous post 中,我展示了如何在运行时使用 F# 反射从 单个 盒装 Map 对象 Map<'k,'v> 获取键。我试图扩展这个想法并传递一个 array 盒装 Map 对象 Map<'k,'v>[],但我找不到扩展原始单对象方法以将对象数组作为参数的方法。我想出了一个可行但看起来不正确的解决方案。我正在寻找一种更好、更惯用的方式。

在下面的代码中,keysFromMap 从单个装箱的Map<'k,'v> 参数中获取一组键; keysFromMapArray 是我第一次尝试从一个装箱的 Map<'k,'v>[] 参数做同样的事情 - 但它不起作用 - 并且 keysFromMapArrayWithCast 确实起作用,但必须在 FromMapArrayWithCast getter 级别进行向下转换似乎并不正确。

我从运行 keysFromMapArray 得到的错误信息(test2 被注释掉了):

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

我的问题:为什么扩展 keysFromMap 方法以获取 Maps 参数数组不起作用,以及如何修复它以使其起作用?

示例代码:

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

【问题讨论】:

    标签: reflection f#


    【解决方案1】:

    这与以下情况相同:

    > type K = static member F(x:int[]) = 3
    let f x = typeof<K>.GetMethod("F").Invoke(null, [|(x:obj[])|])
    f [|2; 3|];;
    System.ArgumentException: Object of type 'System.Object[]' cannot be converted to type 'System.Int32[]'.
       at System.RuntimeType.TryChangeType(Object value, Binder binder, CultureInfo culture, Boolean needsSpecialCast)
       at System.Reflection.MethodBase.CheckArguments(Object[] parameters, Binder binder, BindingFlags invokeAttr, CultureInfo culture, Signature sig)
       at System.Reflection.RuntimeMethodInfo.InvokeArgumentsCheck(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
       at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
       at <StartupCode$FSI_0002>.$FSI_0002.main@()
    Stopped due to error
    

    使中间函数泛型解决了这个问题。

    > type K = static member F(x:int[]) = 3
    let f x = typeof<K>.GetMethod("F").Invoke(null, [|(x:'a[])|]) // unconstrained!
    f [|2; 3|];;
    type K =
      class
        static member F : x:int [] -> int
      end
    val f : x:'a [] -> obj
    val it : obj = 3
    

    这可以应用于keysFromMapArray

        let keysFromMapArray (oMaps : 'a[]) : obj[] =
    

    【讨论】:

    • @ 625 谢谢,我会在回到办公桌前查看。但是,为什么keysFromMap 也不需要这个,这是一个单一的对象参数函数,它与签名keysFromMap (oMap : obj) : obj[] 配合得很好?
    • @Janthelme 因为值类型数组不能与引用类型数组互换,而引用类型数组之间允许协方差。另一方面,值类型和引用类型(包括数组)可以装箱/拆箱到obj 就好了。
    猜你喜欢
    • 1970-01-01
    • 2012-02-05
    • 1970-01-01
    • 2020-03-06
    • 1970-01-01
    • 2012-12-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多