我需要在我无法修改的代码中调用函数。该函数需要obj[]
,我想将其传递给'T[]
。我可以使用Array.map box
,但我试图避免创建一个中间数组。有没有直接的方法将'T[]
转换为obj[]
而不通过Array.map box
或任何其他会创建中间数组的代码?
我尝试编写需要与PersistentVector class from FSharpx.Collections互操作的代码。 (具体来说,我试图在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
线程代码与我当前的问题无关(它在瞬态向量中使用,它允许某些性能改进),因此,为了创建问题的最简单摘要,请删除它。删除与线程相关的代码后,我们有一个Node
定义,如下所示:
type Node(array:obj[]) =
new() = Node([||])
with member this.Array = array
我希望RRB树的实现能够与现有的PersistentVector类平滑地进行互操作,因为所有有效PersistentVector树的集合都是所有有效RRB树集的严格子集。作为该实现的一部分,我有一个继承自RRBNode
的{{1}}类(因此也必须在其构造函数中使用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
调用中给出了类型不匹配错误。 Array.map Node
构造函数使用Node
,错误消息报告&#34;类型obj[]
与类型'T[]
&#34;不兼容。
我尝试解决此问题的一种方法是使用obj[]
和box
。 https://stackoverflow.com/a/7339153/2314532让我相信通过unbox
后跟box
来管理任何类型的数组会导致将该数组转换为unbox
。是的,this is basically a misfeature of the .Net type system会危及类型安全(在编译时传递的强制转换可能会在运行时失败) - 但是因为我需要与PersistentVector中的obj[]
类进行互操作,所以我不能无论如何都类型安全的好处(因为Node
使用Node
而不是歧视联盟。所以对于我的代码的这一部分,我实际上想告诉F#编译器&#34;停止在这里保护我,拜托,我知道我在做什么,并且我已经编写了大量的单元测试&#34; 。但是我尝试使用obj
方法在运行时失败了:
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)
的类型显式化为尽可能简单地阅读这个最小的例子,但我不需要编写它:F#正确地从调用{{{ 1}}在下一行。我的实际代码的等效部分没有明确的类型注释,但objArray
数组类型仍然是推断的,并且它是相同的{{1} } Node(objArray)
通过obj[]
投射,导致我的实际代码中出现int[]
。)
可能有效的另一种方法是将obj[]
的调用插入我的|> box |> unbox
- 创建管道:
InvalidCastException
这就是我想要的(创建一个Array.map box
实例数组,它将成为树中的叶子),但它会在进程中创建一个额外的中间数组。我想让分块的数组直接成为的节点数组,否则我将烧掉O(N)内存并产生不必要的GC压力。我已经考虑过在管道中的某个时刻使用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
的性能影响。将已知大小的数组(此处为32)转换为seqs意味着需要数组(创建Node
个实例)的其他代码必须首先调用Seq.cast
,并实现Seq.cast
使用Node
,因为在一般情况下它不能指望seq的大小。对已经是数组的seq进行了优化,但即使是那个版本的Array.ofSeq
也会创建一个新数组作为其返回值(这正是一般情况下的正确行为,但正是我所说的那样#39} ;我试图避免在这里。)
我有没有办法将我的Array.ofSeq
数组投射到ResizeArray
,故意放弃类型安全,而不创建我尝试过的中间数组这么难以避免?或者我将不得不在C#中编写这一段代码,这样我才能做出F#编译器试图保护我的不安全的事情?
答案 0 :(得分:5)
根据'T
是值还是引用类型,有两种可能的结果。
参考类型
如果'T
是参考类型,那么您的box
unbox
技巧就可以正常使用了:
let strArray = [|"a";"b";"c";"d";"e"|]
let objArray : obj[] = strArray |> box |> unbox
val strArray : string [] = [|"a"; "b"; "c"; "d"; "e"|] val objArray : obj [] = [|"a"; "b"; "c"; "d"; "e"|]
价值类型
如果'T
是值类型,那么,正如您所注意到的那样,转换将在运行时失败。
没有办法使转换成功,因为数组中的值类型尚未加框。没有办法绕过类型系统并直接转换为obj[]
。您将必须为每个元素明确地执行此操作。
let intArray = [|1; 2; 3; 4; 5|]
let objArray : obj[] = intArray |> Array.map (box)
同时处理
您可以编写通用转换函数来检查类型是引用还是值类型,然后执行适当的转换:
let convertToObjArray<'T> (arr : 'T[]) =
if typeof<'T>.IsValueType then
arr |> Array.map (box)
else
arr |> box |> unbox
用法:
convertToObjArray strArray
val it : obj [] = [|"a"; "b"; "c"; "d"; "e"|]
convertToObjArray intArray
val it : obj [] = [|1; 2; 3; 4; 5|]