F#为什么无法确定要调用哪个重载函数?
在F#中,重载决策通常比C#更受限制。为了保证安全性,F#编译器通常会拒绝C#编译器认为是有效的重载。
然而,这种特定情况是真正的歧义。为了利于.NET交互操作,F#编译器对Lambda表达式有一个特殊规定:通常,Lambda表达式将编译为F#函数,但如果期望的类型已知为Func<_,_>
,则编译器将把Lambda转换为.NET委托。这使我们可以使用构建在高阶函数(例如IEnumerable<_>)之上的.NET API,而无需手动转换每个Lambda表达式。
因此,在您的情况下,编译器确实感到困惑:您是要将Lambda表达式保留为F#函数并调用F#重载,还是要将其转换为Func<_,_>
并调用C#重载?
类型注释会是什么样子?
为帮助编译器,您可以明确指定Lambda表达式的类型为string -> string
,如下所示:
let cfg = ThingConfig.FromSettings( (fun s -> foo) : string -> string )
一个稍微更好的方法是将函数定义放在
FromSettings
调用之外:
let getSetting s = foo
let cfg = ThingConfig.FromSettings( getSetting )
这段代码没问题,因为自动转换为 Func<_,_>
仅适用于内联编写的 Lambda 表达式。编译器不会将任何函数都转换为 .NET 委托。因此,在 FromSettings
调用之外声明 getSetting
可以使其类型明确为 string -> string
,从而实现重载决策。
编辑:事实证明上述方法已经失效。当前的 F# 编译器会自动将任何函数转换为 .NET 委托,因此即使将类型指定为 string -> string
,仍然存在歧义性。接下来提供其他解决方案。
顺便提一下类型注释,你可以用类似的方式选择另一个重载:
let cfg = ThingConfig.FromSettings( (fun s -> foo) : Func<_,_> )
或者使用Func
构造函数:
let cfg = ThingConfig.FromSettings( Func<_,_>(fun s -> foo) )
在这两种情况下,编译器都知道参数的类型是 Func<_,_>
,所以可以选择重载。
有更好的模式吗?
重载通常是不好的。它们在某种程度上会掩盖正在发生的事情,导致程序更难调试。我已经数不清有多少个错误,在C#重载决策中选择了IEnumerable
而不是IQueryable
,从而将整个数据库拉到.NET端。
在这种情况下,我通常会声明两个具有不同名称的方法,然后使用CompiledNameAttribute为它们提供从C#查看时的替代名称。例如:
type ThingConfig = ...
[<CompiledName "FromSettingsFSharp">]
static member FromSettings (getSetting : (string -> string)) = ...
[<CompiledName "FromSettings">]
static member FromSettingsCSharp (getSetting : Func<string, string>) = ...
这样,F# 代码将看到两个方法,FromSettings
和 FromSettingsCSharp
,而 C# 代码将看到相同的两个方法,但命名分别为 FromSettingsFSharp
和 FromSettings
。智能感知体验会有点丑陋(但易于理解!),但最终的代码在两种语言中看起来完全一样。
更简单的方法:习惯用法的命名
在 F# 中,以小写字母开头命名函数是惯用法。参见标准库的示例 - Seq.empty
,String.concat
等。因此,在您的情况下,我会创建两个方法,一个用于 F# 命名为 fromSettings
,另一个用于 C# 命名为 FromSettings
:
type ThingConfig = ...
static member fromSettings (getSetting : string -> string) =
...
static member FromSettings (getSetting : Func<string,string>) =
ThingConfig.fromSettings getSetting.Invoke
(请注意,第二种方法可以通过第一种方法来实现;您不必复制&粘贴实现)
Func<_,_>
自动转换为.NET委托),但我认为它不足以被视为那个问题的重复,因为两个问题都有各自的用处。 - rmunn