F# 在没有反射的情况下向下转换数组
F# Downcasting arrays without reflection
我正在开发一个用户可以在运行时向我发送各种对象的程序,而我事先不知道它们的类型(在编译时)。当对象可以向下转换为 any 元素类型的 (F#) 数组时,我想对基础数组执行一些常用操作。例如。 Array.Length
、Array.sub
...
我可以从用户那里得到的对象是 box [| 1; 2; 3 |]
或 box [| "a"; "b"; "c" |]
或任何 'a[]
,但我在编译时不知道 'a
时间。
以下无效:
let arrCount (oarr: obj) : int =
match oarr with
| :? array<_> as a -> a.Length
| :? (obj[]) as a -> a.Length
// | :? (int[]) as a -> a.Length // not an option for me here
// | :? (string[]) as a -> a.Length // not an option for me here
// | ...
| _ -> failwith "Cannot recognize an array"
例如,arrCount (box [| 1; 2; 3 |])
和 arrCount (box [| "a"; "b"; "c" |])
都失败了。
到目前为止我找到的唯一解决方案是使用反射,例如:
type ArrayOps =
static member count<'a> (arr: 'a[]) : int = arr.Length
static member sub<'a> (arr: 'a[]) start len : 'a[] = Array.sub arr start len
// ...
let tryCount (oarr: obj) =
let ty = oarr.GetType()
if ty.HasElementType && ty.BaseType = typeof<System.Array> then
let ety = ty.GetElementType()
let meth = typeof<ArrayOps>.GetMethod("count").MakeGenericMethod([| ety |])
let count = meth.Invoke(null, [| oarr |]) :?> int
Some count
else
None
我的问题:有没有办法在 box [| some elements of some unknown type |]
形式的参数上使用 Array.count
、Array.sub
等函数而不使用反射?
奇怪的是,这在 C# 和 VB 等语言中不是问题,而您可能需要为 F# 做一些额外的工作。通常不能这样做,因为 F# 没有协变类型。
为该功能投票here!
但是我们确实有灵活的类型,它给我们提供了相当有限的逆变,我们可以使用它。
let anyLength (arr : #obj[]) =
arr |> Array.length
而且,
let iarr = [| 1; 2; 3|]
let sarr = [|"a"; "b" |]
anyLength iarr // 3
anyLength sarr // 2
由于 F# 是静态类型安全的,它试图阻止您这样做,这就是为什么它不是微不足道的。将其转换为 array<_>
将不起作用,因为从 F# 的角度来看,array<obj>
不等于 array<int>
等,这意味着您必须检查每个协变类型。
但是,您可以利用 array
也是 System.Array
这一事实,并对其使用 BCL 方法。这不会给你 Array.length
等,因为它们需要类型安全,但你基本上可以通过一点点工作来完成任何操作。
如果您确实有一组有限的已知类型,obj
可以是 array
,我建议您使用这些已知类型创建一个 DU 并创建一个简单的转换器在 array<int>
、array<string>
等上进行匹配,以便您恢复类型安全。
没有任何类型安全,你可以这样做:
let arrCount (x: obj) =
match x with
| null -> nullArg "x cannot be null"
| :? System.Array as arr -> arr.GetLength(0) // must give the rank
| _ -> -1 // or failwith
用法:
> arrCount (box [|1;2;3|]);;
val it : int = 3
> arrCount (box [|"one"|]);;
val it : int = 1
这个关于 SO 的其他答案很好地解释了为什么允许这样的强制转换会使 .NET 类型系统不健全,以及为什么在 F# 中不允许这样:
编辑:第二种选择
如果您不介意装箱整个数组,您可以通过转换整个数组来扩展上述解决方案,一旦您知道它是一个数组。但是,第一种方法(System.Array
)具有 O(1) 性能,而这种方法必然是 O(n):
open System.Collections
let makeBoxedArray (x: obj) =
match x with
| null -> nullArg "x cannot be null"
| :? System.Array as arr ->
arr :> IEnumerable
|> Seq.cast<obj>
|> Seq.toArray
| _ -> failwith "Not an array"
用法:
> makeBoxedArray (box [|1;2;3|]);; // it accepts untyped arrays
val it : obj [] = [|1; 2; 3|]
> makeBoxedArray [|"one"|];; // or typed arrays
val it : obj [] = [|"one"|]
> makeBoxedArray [|"one"|] |> Array.length;; // and you can do array-ish operations
val it : int = 1
> makeBoxedArray (box [|1;2;3;4;5;6|]) |> (fun a -> Array.sub a 3 2);;
val it : obj [] = [|4; 5|]
我正在开发一个用户可以在运行时向我发送各种对象的程序,而我事先不知道它们的类型(在编译时)。当对象可以向下转换为 any 元素类型的 (F#) 数组时,我想对基础数组执行一些常用操作。例如。 Array.Length
、Array.sub
...
我可以从用户那里得到的对象是 box [| 1; 2; 3 |]
或 box [| "a"; "b"; "c" |]
或任何 'a[]
,但我在编译时不知道 'a
时间。
以下无效:
let arrCount (oarr: obj) : int =
match oarr with
| :? array<_> as a -> a.Length
| :? (obj[]) as a -> a.Length
// | :? (int[]) as a -> a.Length // not an option for me here
// | :? (string[]) as a -> a.Length // not an option for me here
// | ...
| _ -> failwith "Cannot recognize an array"
例如,arrCount (box [| 1; 2; 3 |])
和 arrCount (box [| "a"; "b"; "c" |])
都失败了。
到目前为止我找到的唯一解决方案是使用反射,例如:
type ArrayOps =
static member count<'a> (arr: 'a[]) : int = arr.Length
static member sub<'a> (arr: 'a[]) start len : 'a[] = Array.sub arr start len
// ...
let tryCount (oarr: obj) =
let ty = oarr.GetType()
if ty.HasElementType && ty.BaseType = typeof<System.Array> then
let ety = ty.GetElementType()
let meth = typeof<ArrayOps>.GetMethod("count").MakeGenericMethod([| ety |])
let count = meth.Invoke(null, [| oarr |]) :?> int
Some count
else
None
我的问题:有没有办法在 box [| some elements of some unknown type |]
形式的参数上使用 Array.count
、Array.sub
等函数而不使用反射?
奇怪的是,这在 C# 和 VB 等语言中不是问题,而您可能需要为 F# 做一些额外的工作。通常不能这样做,因为 F# 没有协变类型。
为该功能投票here!
但是我们确实有灵活的类型,它给我们提供了相当有限的逆变,我们可以使用它。
let anyLength (arr : #obj[]) =
arr |> Array.length
而且,
let iarr = [| 1; 2; 3|]
let sarr = [|"a"; "b" |]
anyLength iarr // 3
anyLength sarr // 2
由于 F# 是静态类型安全的,它试图阻止您这样做,这就是为什么它不是微不足道的。将其转换为 array<_>
将不起作用,因为从 F# 的角度来看,array<obj>
不等于 array<int>
等,这意味着您必须检查每个协变类型。
但是,您可以利用 array
也是 System.Array
这一事实,并对其使用 BCL 方法。这不会给你 Array.length
等,因为它们需要类型安全,但你基本上可以通过一点点工作来完成任何操作。
如果您确实有一组有限的已知类型,obj
可以是 array
,我建议您使用这些已知类型创建一个 DU 并创建一个简单的转换器在 array<int>
、array<string>
等上进行匹配,以便您恢复类型安全。
没有任何类型安全,你可以这样做:
let arrCount (x: obj) =
match x with
| null -> nullArg "x cannot be null"
| :? System.Array as arr -> arr.GetLength(0) // must give the rank
| _ -> -1 // or failwith
用法:
> arrCount (box [|1;2;3|]);;
val it : int = 3
> arrCount (box [|"one"|]);;
val it : int = 1
这个关于 SO 的其他答案很好地解释了为什么允许这样的强制转换会使 .NET 类型系统不健全,以及为什么在 F# 中不允许这样:
编辑:第二种选择
如果您不介意装箱整个数组,您可以通过转换整个数组来扩展上述解决方案,一旦您知道它是一个数组。但是,第一种方法(System.Array
)具有 O(1) 性能,而这种方法必然是 O(n):
open System.Collections
let makeBoxedArray (x: obj) =
match x with
| null -> nullArg "x cannot be null"
| :? System.Array as arr ->
arr :> IEnumerable
|> Seq.cast<obj>
|> Seq.toArray
| _ -> failwith "Not an array"
用法:
> makeBoxedArray (box [|1;2;3|]);; // it accepts untyped arrays
val it : obj [] = [|1; 2; 3|]
> makeBoxedArray [|"one"|];; // or typed arrays
val it : obj [] = [|"one"|]
> makeBoxedArray [|"one"|] |> Array.length;; // and you can do array-ish operations
val it : int = 1
> makeBoxedArray (box [|1;2;3;4;5;6|]) |> (fun a -> Array.sub a 3 2);;
val it : obj [] = [|4; 5|]