感谢您提出这个问题——这是一个非常好的错误报告,具有简单的重现方式,我不敢相信,但您完全正确。加法可以,但减法不行。
问题在于sub
和add
被编译为通用方法,而LINQ版本调用这些通用方法。内联是在引号存储之后执行的,因此引号代码包含对sub
方法的调用。在正常的F#代码中,这不是问题,因为函数被内联,运算符被解析为某些数值类型上的+或-。
然而,通用版本使用动态查找。如果您查看prim-types.fs:3530
,您会看到:
let inline (+) (x: ^T) (y: ^U) : ^V =
AdditionDynamic<(^T),(^U),(^V)> x y
when ^T : int32 and ^U : int32 = (# "add" x y : int32 #)
when ^T : float and ^U : float = (# "add" x y : float #)
// ... lots of other cases
AdditionDynamic
是从通用方法中调用的函数,它执行动态查找,虽然较慢,但能正常工作。有趣的是,在减法运算符的情况下,F#库不包含动态实现:
[<NoDynamicInvocation>]
let inline (-) (x: ^T) (y: ^U) : ^V =
((^T or ^U): (static member (-) : ^T * ^U -> ^V) (x,y))
when ^T : int32 and ^U : int32 = (# "sub" x y : int32 #)
when ^T : float and ^U : float = (# "sub" x y : float #)
// ... lots of other cases
我不知道为什么会出现这种情况——我认为没有任何技术原因,但这解释了你报告的行为。如果你使用ILSpy查看已编译的代码,你会发现
add
方法有一些动作,而
sub
方法只会抛出异常(这就是异常的来源)。
至于解决方法,你需要以一种不使用泛型减号操作符的方式编写代码。可能最好的选择是避免使用内联函数(可以通过使用
sub_int
或
sub_float
),或者编写自己的
sub
动态实现(这可以通过使用DLR来实现,具体请参见
此帖子)。
NoDynamicInvocation
操作符“-”的麻烦调用被隐藏在编译时,并且无法解决。 - Stephen Swensen