F#函数转System.Func抛出TargetException 咨询MethodInfo获取方案
问题1:抛出异常的根本原因
- 核心原因是F#原生函数类型和.NET委托类型的本质差异,两种写法下编译器的转换逻辑完全不同:
- F#里的
unit -> 'a、'a -> 'b这类函数,本质是FSharp.Core定义的FSharpFunc<,>抽象类的实例,不是原生.NET委托。你在模块的from0/from1方法里显式写Func<'a>(f)做转换时,相当于基于已有的FSharpFunc实例新构造了一个Func委托,这个委托的Method属性指向的是FSharpFunc.Invoke的适配包装方法——这是个实例方法,绑定的目标对象是你传入的那个FSharpFunc实例。你后续调用Invoke(null, ...)时给target传了null,自然会触发"非静态方法需要目标实例"的异常。 - 而你直接把匿名函数传入接收
Func<>类型参数的静态方法时,F#编译器会做隐式委托转换优化:不会生成中间的FSharpFunc包装层,直接把你写的匿名函数编译成所在模块/类下的静态方法,再把这个静态方法直接绑定到生成的Func委托上。这时候拿到的Method本身就是静态方法,target就是null,调用时传null自然不会报错。
- F#里的
- 你可以做简单验证:第一种写法拿到的MethodInfo的
DeclaringType是FSharpFunc相关的运行时类型,第二种拿到的就是你写代码所在模块的生成类型,差异非常明显。
问题2:当前推荐的MethodInfo获取方式
早年讨论帖里提到的很多反射拆FSharpFunc内部字段的写法已经过时,现在更稳定简洁的方案按场景选择即可:
- 优先选直接用委托类型做参数的写法:就是你第二种实现,把入参直接定义成
Func<>/Action类型,利用编译器的隐式委托转换直接取f.Method。这种写法从F# 3.0到最新的F# 8都稳定可用,没有额外包装开销,也不会依赖FSharpFunc的内部实现,是最靠谱的首选方案。尤其是F# 6之后对委托隐式转换的支持进一步增强,不需要手动new委托,只要类型匹配编译器自动完成转换,代码写起来非常简洁。 - 如果你的场景必须接收F#原生函数类型(比如要兼容柯里化的高阶函数逻辑),可以用表达式树提取原始方法信息,绕开委托包装的问题,示例代码如下:
open System.Linq.Expressions let extractMethod (f: 'T -> 'U) = let param = Expression.Parameter(typeof<'T>, "arg") let invokeExpr = Expression.Invoke(Expression.Constant(f), param) (invokeExpr :?> MethodCallExpression).Method - 额外提醒:如果传入的是捕获了外部变量的闭包函数,不管用哪种方式拿到的都是闭包生成类的实例方法,调用时必须传入闭包实例作为target,不能传null,否则还是会抛同样的异常。
内容的提问来源于stack exchange,提问作者farlee2121
相关产品推荐
相关产品推荐

