如何创建特定运行时类型的空列表

时间:2016-12-20 23:58:31

标签: list f#

给定.Net类型,比如typeof<string>,在运行时如何创建等效的string list = []

我的动机是,当使用FSharpValue.MakeRecord根据解析的值创建记录时,需要将值作为obj[]传递。我一直在使用box来构建参数,并且除了列表之外还有效。我遇到的问题是空的无类型列表列表不能装箱然后取消装箱。返回的具体错误是:

System.InvalidCastException: Unable to cast object of type 
'Microsoft.FSharp.Collections.FSharpList`1[System.Object]' 
to type 
'Microsoft.FSharp.Collections.FSharpList`1[System.String]'.

空类型列表可以装箱和取消装箱,所以我试图找到一种方法将列表转换为运行时类型,例如typeof&lt;&gt;返回的类型但没有运气。

type Test = {Names : string list}
// fails
let genericList = []
{Names = unbox (box genericList)}

//works
let typedList : string list = []
{Names = unbox (box typedList)}

//works
let genericNonEmptyList = ["Bill"]
{Names = unbox (box genericNonEmptyList)}

5 个答案:

答案 0 :(得分:4)

使用反射,您可以获取List模块并调用通用empty方法:

open System
open System.Reflection

let emptyList (t:Type) =
    Assembly.GetAssembly(typeof<_ list>)
        .GetType("Microsoft.FSharp.Collections.ListModule")
        .GetMethod("Empty", BindingFlags.Static ||| BindingFlags.Public)
        .MakeGenericMethod(t)
        .Invoke(null, [||])

使用如下:

let itemType = typeof<string>
let emptyStringList = emptyList(itemType) :?> string list

如果您经常打电话,请考虑缓存(将执行时间减少~1 / 3):

let emptyList =
    let empty =
        Assembly.GetAssembly(typeof<_ list>)
            .GetType("Microsoft.FSharp.Collections.ListModule")
            .GetMethod("Empty", BindingFlags.Static ||| BindingFlags.Public)
    fun (t:Type) -> empty.MakeGenericMethod(t).Invoke(null, [||])

答案 1 :(得分:3)

@CaringDev使用.NET反射的答案很好,但您也可以使用F#特定的反射模块来创建联合案例的实例:

let empty ty = 
    let uc = 
        Reflection.FSharpType.GetUnionCases(typedefof<_ list>.MakeGenericType [|ty|]) 
        |> Seq.filter (fun uc -> uc.Name = "Empty") 
        |> Seq.exactlyOne
    Reflection.FSharpValue.MakeUnion(uc, [||])

答案 2 :(得分:3)

让我再添加一个替代答案 - 尽管两种现有方法都有效,但它们依赖于理解F#如何表示列表。在第一种情况下,您需要知道有Empty方法,在第二种情况下,您需要知道有一个名为Empty的联合案例。

我通常更喜欢通过定义帮助器类型并在我的自定义类型上使用反射来实现此目的:

type ListHelper =
  static member Empty<'T>() : list<'T> = []

let makeEmpty =
  let empty = typeof<ListHelper>.GetMethod("Empty")
  let emptyArr : obj[] = [| |]
  fun ty -> empty.MakeGenericMethod([| ty |]).Invoke(null, emptyArr)

这为您提供了非常简单的功能,可以缓存MethodInfo(您甚至可以使用Expression预编译和缓存调用)并且不依赖于聪明的技巧。

答案 3 :(得分:1)

我迟到了,但是我试图做同样的事情。我找到了一种方法来进行模式匹配:

let emptyListFromExample e =
    match [e] with
    | [] -> []
    | x::xs -> xs

这将为您提供任何类型的空列表,前提是您可以构造该类型的值以将其启动。

答案 4 :(得分:0)

如何使用Seq.cast转换空通用列表?

type Test = {Names : string list}
let genericList = []
let test = {Names = unbox (box genericList ) |> Seq.cast<string> |> Seq.toList}
test.Names //val it : string list = []