使用生成的类型调用MakeGenericTypeDefinition时出现NotSupportedException

tak*_*gen 6 f# type-providers

我正在尝试在F#中实现一个简单的生成类型提供程序.弹出的一件事是,显然生成一个包含泛型方法调用的引用,其中泛型类型参数被生成的类型替换不受支持.一个演示此行为的简单示例:

首先,定义一个通用方法和一些utils:

module Printer =

    let print (name: string) (value: 'T) =
        printfn "%s: %O" name value

module Provided = 

    let property name propertyType value =
        ProvidedProperty(name, propertyType, GetterCode = (fun _ -> value))

    let ctor () = ProvidedConstructor([], InvokeCode = (fun _ -> <@@ () @@>))
Run Code Online (Sandbox Code Playgroud)

然后,在类型提供程序中,another定义以下简单类型:

let providerTempAssembly = Path.ChangeExtension(Path.GetTempFileName(), ".dll") |> ProvidedAssembly
let provider = ProvidedTypeDefinition(asm, ns, "Provider", Some typeof<obj>, IsErased = false, HideObjectMethods = true)

// Create empty "AnotherType" type with parameterless constructor
let another = ProvidedTypeDefinition("AnotherType", Some typeof<obj>, IsErased = false)
another.AddMember <| Provided.ctor()
provider.AddMember another
Run Code Online (Sandbox Code Playgroud)

最后,target使用尝试调用的PrintMembers()方法键入Printer.print:

let target = ProvidedTypeDefinition("TargetType", Some typeof<obj>, IsErased = false)

let properties = [
    Provided.property "Id" typeof<int> <| Expr.Value 10;
    Provided.property "Name" typeof<string> <| Expr.Value "abc";
    Provided.property "AnotherProp" another <| Expr.Value null
]

target.AddMembers properties

target.AddMember <| ProvidedMethod("PrintMembers", [], typeof<unit>, InvokeCode = (fun args ->
    let this = args.[0]

    // MethodInfo of Printer.print<'T> method definition where concrete type of 'T is not specified
    let printMethod = 
        match <@@ Printer.print "foo" 42 @@> with 
        | Call(_, x, _) -> x.GetGenericMethodDefinition()

    let printStatements = 
        properties
        |> List.map (fun prop ->
            try
                let genericPrint = printMethod.MakeGenericMethod(prop.PropertyType)
                Expr.Call(genericPrint, [Expr.Value(prop.Name); Expr.PropertyGet(this, prop)]) // fails here
            with
            | ex -> 
                printfn "Failed to generage call of Printer.print for property %s of type %O. Error details: %O" prop.Name prop.PropertyType ex
                reraise())

    // Generates sequence of expressions that call Printer.print for each property
    List.foldBack (fun t acc -> Expr.Sequential(t, acc)) printStatements <| Expr.Value(())))

target.AddMember <| Provided.ctor()

provider.AddMember target
providerTempAssembly.AddTypes [provider]
this.AddNamespace(ns, [provider])
Run Code Online (Sandbox Code Playgroud)

可在此处找到包含所需样板的完整源代码

尝试使用此类型提供程序会导致以下错误:

Failed to generage call of Printer.print for property AnotherProp of type AnotherType. Error details: System.NotSupportedException: Specified method is not supported.
at System.Reflection.Emit.MethodOnTypeBuilderInst.GetParametersInternal () <0x1a27320 + 0x00027> in <filename unknown>:0 
at System.Reflection.Emit.MethodOnTypeBuilderInst.GetParameters () <0x1a272f0 + 0x00020> in <filename unknown>:0 
at Microsoft.FSharp.Quotations.PatternsModule.mkStaticMethodCall (System.Reflection.MethodInfo minfo, Microsoft.FSharp.Collections.FSharpList`1 args) <0x5301740 + 0x00035> in <filename unknown>:0 
at Microsoft.FSharp.Quotations.FSharpExpr.Call (System.Reflection.MethodInfo methodInfo, Microsoft.FSharp.Collections.FSharpList`1 arguments) <0x53016e0 + 0x0004f> in <filename unknown>:0 
at <StartupCode$GenericMethodsTypeProvider>.$GenericMethodsTypeProvider+printStatements@62.Invoke (ProviderImplementation.ProvidedTypes.ProvidedProperty prop) <0x5300250 + 0x000ff> in <filename unknown>:0 
Run Code Online (Sandbox Code Playgroud)

所以,Printer.print<int>Printer.print<string>可以调用,但不能Printer.print<AnotherType>.

它是类型提供者限制还是有另一种方法来生成引用,其中可以使用生成的类型调用泛型方法?

UPD:显然,尝试调用泛型类的非泛型方法,其中一个类型参数绑定到生成类型也失败了NotSupportedException.假设我想生成以下引文:

<@@ 
     if this.Property.IsSome 
     then Printer.print "Property" this.Property.Value
     else Printer.print "Property" "Absolutely nothing"
@@>
Run Code Online (Sandbox Code Playgroud)

对于类型的属性AnotherType option(AnotherType在上面生成).以下代码:

if property.PropertyType.IsGenericType && 
    property.PropertyType.GetGenericTypeDefinition() = typedefof<option<_>> 
then
    let isSomeProp = property.PropertyType.GetProperty("IsSome") // fails here
    let valueProp = property.PropertyType.GetProperty("Value")
    let underlyingType = property.PropertyType.GenericTypeArguments.[0]
    let genericPrint = printMethod.MakeGenericMethod(underlyingType)

    let propertyName = property.Name
    let isSome = Expr.PropertyGet(isSomeProp, [Expr.PropertyGet(this, property)])
    let getValue = Expr.PropertyGet(Expr.PropertyGet(this, property), valueProp)
    let printPropertyValue = Expr.Call(genericPrint, [Expr.Value(propertyName); getValue])

    Expr.IfThenElse(isSome, printPropertyValue,<@@ Printer.print propertyName "Absolutely nothing" @@>)
Run Code Online (Sandbox Code Playgroud)

失败了:

Failed to generage call of Printer.print for property MaybeAnotherProp of type Microsoft.FSharp.Core.FSharpOption`1[AnotherType]. Error details: System.NotSupportedException: Specified method is not supported.
at System.Reflection.MonoGenericClass.GetPropertyImpl (System.String name, BindingFlags bindingAttr, System.Reflection.Binder binder, System.Type returnType, System.Type[] types, System.Reflection.ParameterModifier[] modifiers) <0x1a54ff0 + 0x00027> in <filename unknown>:0 
at System.Type.GetProperty (System.String name) <0x1968c30 + 0x00055> in <filename unknown>:0 
at <StartupCode$GenericMethodsTypeProvider>.$GenericMethodsTypeProvider+printStatements@64.Invoke (ProviderImplementation.ProvidedTypes.ProvidedProperty property) <0x5346898 + 0x000eb> in <filename unknown>:0 
Run Code Online (Sandbox Code Playgroud)

完整代码在更新的要点

那么,为什么不支持这些操作并且有任何解决方法吗?

UPD2: 确实,@ kvb建议使用ProvidedTypeBuilder.MakeGenericMethod而不是someMethodInfo.MakeGenericMethod解决第一种情况.在这种情况下,确实可以使用生成的类型调用泛型方法.

但是,显然当类型参数绑定到生成的类型时,不可能调用其中一个参数也是泛型类型的泛型方法.换句话说,可以调用方法

let foo<'T> (x: 'T) = printfn "%A" x
Run Code Online (Sandbox Code Playgroud)

但不是

let foo<'T> (x: option<'T>) = printfn "%A" x
Run Code Online (Sandbox Code Playgroud)

显然,这是反射和类型提供者组合的限制.我创建了一个包含更多信息的问题.

解决方法是将泛型类型的参数作为实例传递obj:

let foo<'T> (x: obj) = x :?> option<'T> |> printfn "%A"
Run Code Online (Sandbox Code Playgroud)