我可以在不使用 `Array.map box` 的情况下将 'T[] 参数传递给需要 obj[] 的函数吗?
Can I pass a 'T[] parameter to a function that wants obj[] without using `Array.map box`?
简短版本:
我需要在无法修改的代码中调用一个函数。该函数接受一个 obj[]
,我想将它传递给一个 'T[]
。我可以使用 Array.map box
,但我试图避免创建中间数组。有没有直接的方法将 'T[]
转换为 obj[]
而无需通过 Array.map box
或任何其他会创建中间数组的代码?
长版:
我正在尝试编写需要与 F# 中的 PersistentVector class from FSharpx.Collections. (Specifically, I'm trying to implement RRB-Trees 互操作的代码。 PersistentVector 基本上是一个分支因子为 32 的 B 树。树中的每个节点都包含以下两种情况之一:其他节点(如果该节点不是叶节点),或者存储在树中的项目(如果该节点是叶节点)。现在,在 F# 中表示此数据结构的最自然方式是使用像 type Node<'T> = TreeNode of Node[] | LeafNode of 'T[]
这样的可区分联合。但是出于我假设的性能原因,FSharpx.Collections.PersistentVector 代码改为将其节点 class 定义如下:
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 class 顺利互操作,因为所有有效 PersistentVector 树的集合是所有有效 RRB 树集合的严格子集。作为该实现的一部分,我有一个 RRBNode
class 继承自 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
构造函数接受一个obj[]
,错误信息报告说"the type 'T[]
is not compatible with the type obj[]
".
我尝试解决此问题的一种方法是使用 box
和 unbox
。 led me to believe that piping an array of any type through box
followed by unbox
would lead to casting that array to obj[]
. Yes, this is basically a misfeature of the .Net type system 危及类型安全(在编译时通过的转换可能在运行时失败)——但是因为我需要与来自 PersistentVector 的 Node
class 互操作,所以我不 无论如何都有类型安全的好处(因为Node
使用了obj
而不是可区分的联合)。所以对于我的这一部分代码,我实际上是想告诉 F# 编译器 "Stop protecting me here, please, I know what I'm doing and I've written extensive unit tests"。但是我尝试使用 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)
(我明确了 objArray
的类型,以便尽可能轻松地阅读这个最小的示例,但我不需要编写它:F# 从对 [= 的调用中正确地推断出其所需的类型45=] 在下一行。我的实际代码的等效部分没有显式类型注释,但仍推断出 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
这就是我想要的(创建一个 Node
个实例的数组,它将成为树中的叶子),但它在这个过程中创建了一个额外的中间数组。我想让分块数组直接成为节点数组,否则我将消耗 O(N) 内存并产生不必要的 GC 压力。我考虑过在管道中的某个时刻使用 Seq.cast
,但我担心使用 Seq.cast
的性能影响。将已知大小的数组(此处为 32)转换为 seqs 意味着其他需要数组(以创建 Node
实例)的代码必须先调用 Array.ofSeq
,然后实现 Array.ofSeq
使用 ResizeArray
因为在一般情况下它不能指望序列的大小。对已经是数组的 seqs 进行了优化,但即使 Array.ofSeq
的那个版本也会创建一个新数组作为其 return 值(这正是一般情况下的正确行为,但正是我尽量避开这里)。
我有什么办法可以将我的 'T[]
数组转换为 obj[]
, 故意放弃类型安全性 ,而不创建我的中间数组一直在努力避免?或者我是否必须在 C# 中编写这一点代码,以便我可以执行 F# 编译器试图保护我免受的不安全操作?
根据 '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|]
简短版本:
我需要在无法修改的代码中调用一个函数。该函数接受一个 obj[]
,我想将它传递给一个 'T[]
。我可以使用 Array.map box
,但我试图避免创建中间数组。有没有直接的方法将 'T[]
转换为 obj[]
而无需通过 Array.map box
或任何其他会创建中间数组的代码?
长版:
我正在尝试编写需要与 F# 中的 PersistentVector class from FSharpx.Collections. (Specifically, I'm trying to implement RRB-Trees 互操作的代码。 PersistentVector 基本上是一个分支因子为 32 的 B 树。树中的每个节点都包含以下两种情况之一:其他节点(如果该节点不是叶节点),或者存储在树中的项目(如果该节点是叶节点)。现在,在 F# 中表示此数据结构的最自然方式是使用像 type Node<'T> = TreeNode of Node[] | LeafNode of 'T[]
这样的可区分联合。但是出于我假设的性能原因,FSharpx.Collections.PersistentVector 代码改为将其节点 class 定义如下:
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 class 顺利互操作,因为所有有效 PersistentVector 树的集合是所有有效 RRB 树集合的严格子集。作为该实现的一部分,我有一个 RRBNode
class 继承自 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
构造函数接受一个obj[]
,错误信息报告说"the type 'T[]
is not compatible with the type obj[]
".
我尝试解决此问题的一种方法是使用 box
和 unbox
。 led me to believe that piping an array of any type through box
followed by unbox
would lead to casting that array to obj[]
. Yes, this is basically a misfeature of the .Net type system 危及类型安全(在编译时通过的转换可能在运行时失败)——但是因为我需要与来自 PersistentVector 的 Node
class 互操作,所以我不 无论如何都有类型安全的好处(因为Node
使用了obj
而不是可区分的联合)。所以对于我的这一部分代码,我实际上是想告诉 F# 编译器 "Stop protecting me here, please, I know what I'm doing and I've written extensive unit tests"。但是我尝试使用 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)
(我明确了 objArray
的类型,以便尽可能轻松地阅读这个最小的示例,但我不需要编写它:F# 从对 [= 的调用中正确地推断出其所需的类型45=] 在下一行。我的实际代码的等效部分没有显式类型注释,但仍推断出 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
这就是我想要的(创建一个 Node
个实例的数组,它将成为树中的叶子),但它在这个过程中创建了一个额外的中间数组。我想让分块数组直接成为节点数组,否则我将消耗 O(N) 内存并产生不必要的 GC 压力。我考虑过在管道中的某个时刻使用 Seq.cast
,但我担心使用 Seq.cast
的性能影响。将已知大小的数组(此处为 32)转换为 seqs 意味着其他需要数组(以创建 Node
实例)的代码必须先调用 Array.ofSeq
,然后实现 Array.ofSeq
使用 ResizeArray
因为在一般情况下它不能指望序列的大小。对已经是数组的 seqs 进行了优化,但即使 Array.ofSeq
的那个版本也会创建一个新数组作为其 return 值(这正是一般情况下的正确行为,但正是我尽量避开这里)。
我有什么办法可以将我的 'T[]
数组转换为 obj[]
, 故意放弃类型安全性 ,而不创建我的中间数组一直在努力避免?或者我是否必须在 C# 中编写这一点代码,以便我可以执行 F# 编译器试图保护我免受的不安全操作?
根据 '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|]