使用生成的类型

时间:2016-04-18 20:10:16

标签: 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 _ -> <@@ () @@>))

然后,在类型提供程序中,定义了以下简单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

最后,使用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])

可以找到包含所需样板的完整源代码here

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

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 

因此,可以调用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"
@@>

表示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" @@>)

失败了:

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 

完整代码为in updated gist

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

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

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

let foo<'T> (x: 'T) = printfn "%A" x

但不是

let foo<'T> (x: option<'T>) = printfn "%A" x

显然,这是反射和类型提供者组合的限制。我创建了an issue,其中包含更多信息。

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

let foo<'T> (x: obj) = x :?> option<'T> |> printfn "%A"

0 个答案:

没有答案