2016-12-20 42 views
6

的空单,说typeof<string>,在运行时如何创建一个的string list = []等价?如何创建给定的.NET类型的特定运行时类型

我的动机是,当使用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 <>返回的类型,但没有运气。

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

难道Seq.cast你想要做什么?对不起,如果我误解了OP:'genericList |> Seq.cast '。 – s952163

+0

约翰,任何线索如何用反射做到这一点?我已经“哼了一声”,但是据我所知,没有明确的方法可以做到这一点。 – jbeeko

回答

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 = [] 
+0

确实'[] |> Seq.cast |> Seq.toList'工作,但只有编译时间类型'string'知道。在我的情况下,我正在运行时构建一个列表,已经在运行时类型中传入。在这种情况下,我不能这样做,因为不能使用typeof 的运行时类型。 – jbeeko

+0

我明白了。我担心这会成为一个问题。 :-( – s952163

3

使用反射,你可以得到的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, [||]) 
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, [||]) 
+0

我怎么样,这是简洁和利用的事实列表是联合类型。将缓存'Reflection.FSharpType.GetUnionCases(typedefof <_ list>'类似于@caringdev建议有助于提高性能? – jbeeko

+1

没有,不像一般'MethodInfo',有没有这样的事,作为一个通用的'UnionCaseInfo',所以没有类似的方法,在非一般的情况下,你可能想缓存并使用的,而不是调用'FSharpValue FSharpValue.PreComputeUnionConstructor'的'结果。 。MakeUnion' – kvb

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来预编译和缓存调用)并不依赖于聪明的技巧。

+1

这是一个很好的选择,但是决定在运行时工会案的名字很容易:'让名=让(Quotations.Patterns.NewUnionCase(UC,_))=在uc.Name' <@ [] @>。 – kvb

+0

不幸的是像@kvb和@CaringDev的解决方案,这似乎并没有做很正确的事情。运行'makeEmpty的typeof “例如返回一个签名'val it:obj = []'但是需要的是'val it:string list = []' – jbeeko

+1

@jbeeko你可以添加':?>字符串列表来输出结果 - 但是如果你是使用反射,然后我假设你不会静态地知道元素的类型(你只有运行时的'System.Type')。如果你知道静态类型,你根本就不需要反思。 –

1

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

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

这会给你任何类型的空单,只要你能构建一个类型的值来启动它。