我可以将'T []参数传递给想要obj []而不使用`Array.map box`的函数吗?

rmu*_*unn 2 arrays f# covariance

简短版本:

我需要在代码中调用函数我无法修改.该函数需要一个obj[],我想通过它'T[].我可以使用Array.map box,但我试图避免创建一个中间数组.是否有直接的方法将a转换'T[]obj[]不通过Array.map box或任何其他将创建中间数组的代码?

长版:

我正在尝试编写需要与FSharpx.Collections中PersistentVector类进行互操作的代码.(具体来说,我正在尝试在F#中实现RRB-Trees).PersistentVector基本上是一个B树,其分支因子为32.树中的每个节点都包含两个内容之一:其他节点(如果节点不是叶节点),或者存储在树中的项(如果节点)是一个叶子节点).现在,在F#中表示此数据结构的最自然的方式是使用类似的区分联合type Node<'T> = TreeNode of Node[] | LeafNode of 'T[].但是我认为是性能原因,FSharpx.Collections.PersistentVector代码定义了它的Node类,如下所示:

type Node(thread,array:obj[]) =
    let thread = thread
    new() = Node(ref null,Array.create Literals.blockSize null)
    with
        static member InCurrentThread() = Node(ref Thread.CurrentThread,Array.create Literals.blockSize null)
        member this.Array = array
        member this.Thread = thread
        member this.SetThread t = thread := t
Run Code Online (Sandbox Code Playgroud)

线程代码与我当前的问题无关(它在瞬态向量中使用,它允许某些性能改进),所以让我们删除它以便创建问题的最简单的摘要.删除与线程相关的代码后,我们有一个如下所示的Node定义:

type Node(array:obj[]) =
    new() = Node([||])
    with member this.Array = array
Run Code Online (Sandbox Code Playgroud)

我希望RRB树的实现能够与现有的PersistentVector类平滑地进行互操作,因为所有有效的PersistentVector树的集合都是所有有效RRB树集的严格子集.作为该实现的一部分,我有一个RRBNode继承自的类Node(因此也必须obj[]在其构造函数中使用参数),并且我经常需要创建其中一个Node或的新实例RRBNode.例如,我的实现RRBTree.ofArray基本上是这样的:

let ofArray<'T> (arr:'T[]) =
    let leaves = arr |> Array.chunkBySize 32 |> Array.map Node
    // More code here to build a tree above those leaf nodes
Run Code Online (Sandbox Code Playgroud)

或者说,我想像那样定义它,但我不能.上面的代码在Array.map Node调用时给出了类型不匹配错误.该Node构造函数采用obj[],并且错误信息报告说:"类型'T[]不与类型兼容obj[]".

我试图解决这个问题的一种方法是使用boxunbox./sf/answers/513740741/让我相信,通过box后面的任何类型的数组管道unbox将导致将该数组转换为obj[].是的,这是基本的.NET类型系统的不良特性进而损害类型安全(即通过在编译时可能会在运行时失败投) -而是因为我需要与互操作Node从PersistentVector类,我不具备的优势无论如何都是安全类型(因为Node已经使用obj而不是有区别的联盟).所以对于我的代码的这一部分,我实际上想告诉F#编译器"请停止保护我,请知道我正在做什么,并且我已经编写了大量的单元测试".但是我box >> unbox在运行时尝试使用该方法失败了:

let intArray = [|1;2;3;4;5|]
let intNode = Node(intArray) // Doesn't compile: Type mismatch. Expecting obj[] but got int[]

let objArray : obj[] = intArray |> box |> unbox // Compiles, but fails at runtime: InvalidCastException
let objNode = Node(objArray)
Run Code Online (Sandbox Code Playgroud)

(我做了objArray显式的类型,尽可能简单地阅读这个最小的例子,但我不需要写它:F#正确地从Node(objArray)下一行的调用中推断出它所需的类型.我的实际代码的等效部分没有显式类型注解,但obj[]数组类型仍是推断,这是相同的int[],以obj[]投,通过|> box |> unbox,这是导致InvalidCastException我的实际代码.)

可能有效的另一种方法是将调用插入Array.map box到我的Node创建管道中:

let ofArray<'T> (arr:'T[]) =
    let leaves = arr |> Array.chunkBySize 32 |> Array.map (Array.map box >> Node)
    // More code here to build a tree above those leaf nodes
Run Code Online (Sandbox Code Playgroud)

这就是我想要的(创建一个Node实例数组,它将成为树中的叶子),但它会在进程中创建一个额外的中间数组.我想让chunked数组直接成为Node数组,否则我将烧掉O(N)内存并产生不必要的GC压力.我考虑过Seq.cast在管道中的某些时候使用,但我担心使用的性能影响Seq.cast.将已知大小的数组(此处为32)转换为seqs意味着需要数组(创建Node实例)的其他代码必须首先调用Array.ofSeq,并且Array.ofSeq实现时ResizeArray因为它不能指望seq中的seqs的大小一般情况.对于已经是数组的seqs进行了优化,但即使是那个版本也会Array.ofSeq创建一个新数组作为其返回值(这正是一般情况下的正确行为,但正是我在这里要避免的).

有什么办法让我投我的'T[]阵列obj[],故意丧失类型安全,没有创造我一直在努力地试图避免中间阵列?或者我将不得不在C#中编写这一段代码,这样我才能做出F#编译器试图保护我的不安全的事情?

The*_*ght 5

根据'T值是值还是引用类型,有两种可能的结果.

参考类型

如果'T是引用类型,那么你的box unbox技巧将正常工作:

let strArray = [|"a";"b";"c";"d";"e"|]
let objArray : obj[] = strArray |> box |> unbox
Run Code Online (Sandbox Code Playgroud)
val strArray : string [] = [|"a"; "b"; "c"; "d"; "e"|]
val objArray : obj [] = [|"a"; "b"; "c"; "d"; "e"|]
Run Code Online (Sandbox Code Playgroud)

价值类型

如果'T是值类型,那么,正如您所注意到的,转换将在运行时失败.

根本没有办法使转换成功,因为数组中的值类型尚未加框.没有办法绕过类型系统并obj[]直接转换.您将必须为每个元素明确地执行此操作.

let intArray = [|1; 2; 3; 4; 5|]
let objArray : obj[] = intArray |> Array.map (box)
Run Code Online (Sandbox Code Playgroud)

处理两者

您可以编写通用转换函数来检查类型是引用还是值类型,然后执行适当的转换:

let convertToObjArray<'T> (arr : 'T[]) =
    if typeof<'T>.IsValueType then
        arr |> Array.map (box)
    else
        arr |> box |> unbox
Run Code Online (Sandbox Code Playgroud)

用法:

convertToObjArray strArray
Run Code Online (Sandbox Code Playgroud)
val it : obj [] = [|"a"; "b"; "c"; "d"; "e"|]
Run Code Online (Sandbox Code Playgroud)
convertToObjArray intArray
Run Code Online (Sandbox Code Playgroud)
val it : obj [] = [|1; 2; 3; 4; 5|]
Run Code Online (Sandbox Code Playgroud)