我正在尝试在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"