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