C#和F#在默认(或可选)参数的实现上有所不同。
在C#语言中,当您为参数添加默认值时,您不会改变其基础类型(即参数的类型)。实际上,C#中的可选参数只是一种轻量级的语法糖:
class CSharpOptionalArgs
{
public static void Foo(int n = 0) {}
}
// Somewhere in the call site
CSharpOptionalArgs.Foo();
// Call to Foo() will be transformed by C# compiler
// *at compile time* to something like:
const int nArg = GetFoosDefaultArgFromTheMetadata();
CSharpOptionalArgs.Foo(nArg);
但是 F# 以一种不同的方式实现了此功能。与 C# 不同,F# 可选参数在 调用方 而非 被调用方 处解析:
但F#以不同的方式实现此功能。与C#不同,F#可选参数在调用方处而非被调用方处解析:
type FSharpOptionalArgs() =
static let defaultValue() = 42
static member public Foo(?xArg) =
// Callee site decides what value to use if caller does not specifies it
let x = defaultArg xArg (defaultValue())
printfn "x is %d" x
这个实现是非常合理的,而且更加强大。C# 的可选参数只限于编译时常量(因为可选参数存储在程序集元数据中)。在 F# 中,默认值可能不那么明显,但我们可以使用任意表达式作为默认值。到目前为止一切都很好。
F# 可选参数由 F# 编译器转换为 Microsoft.FSharp.Core.FSharpOption<'a>
,它是一个引用类型。这意味着每次在 F# 中调用带有可选参数的方法都会导致在托管堆上进行额外的分配,并对垃圾回收造成压力。
**EDITED**
// This call will NOT lead to additional heap allocation!
FSharpOptionalArgs.Foo()
// But this one will do!
FSharpOptionalArgs.Foo(12)
我不担心应用程序代码,但这种行为可能会严重降低库的性能。如果某个具有可选参数的库方法每秒被调用数千次会怎么样?
我觉得这种实现方式确实很奇怪。但也许有一些规则要求库开发人员避免使用此功能,或者 F# 团队将在未来版本中更改此行为?
以下单元测试证明可选参数是引用类型:
[<TestFixture>]
type FSharpOptionalArgumentTests() =
static member public Foo(?xArg) =
// Callee site decides what value to use if caller does not specifies it
let x = defaultArg xArg 42
()
[<Test>]
member public this.``Optional argument is a reference type``() =
let pi = this.GetType().GetMethod("Foo").GetParameters() |> Seq.last
// Actually every optional parameter in F# is a reference type!!
pi.ParameterType |> should not' (equal typeof<int>)
pi.ParameterType.IsValueType |> should be False
()