我需要与一些C#代码进行互操作,并使用F#。空值是可能给出的值,因此我需要检查该值是否为空。文档建议使用以下模式匹配:
match value with
| null -> ...
| _ -> ...
我遇到的问题是原始代码以C#结构化:
if ( value != null ) {
...
}
在 F# 中要如何实现相同的功能?是否有用于模式匹配的无操作(no-op)?是否有一种方法可以使用 if 语句检查 null 值?
由于某些原因(我还没有调查清楚),not (obj.ReferenceEquals(value, null))
的性能比 value <> null
好得多。我编写了很多用于C#的F#代码,因此我保留了一个"interop"模块来简化处理null
。另外,如果您希望在匹配模式时首先使用“正常”情况,可以使用活动模式:
let (|NotNull|_|) value =
if obj.ReferenceEquals(value, null) then None
else Some()
match value with
| NotNull ->
//do something with value
| _ -> nullArg "value"
如果你想要一个简单的if
语句,这个也可以工作:
let inline notNull value = not (obj.ReferenceEquals(value, null))
if notNull value then
//do something with value
以下是一些基准测试和关于性能差异的额外信息:
let inline isNull value = (value = null)
let inline isNullFast value = obj.ReferenceEquals(value, null)
let items = List.init 10000000 (fun _ -> null:obj)
let test f = items |> Seq.forall f |> printfn "%b"
#time "on"
test isNull //Real: 00:00:01.512, CPU: 00:00:01.513, GC gen0: 0, gen1: 0, gen2: 0
test isNullFast //Real: 00:00:00.195, CPU: 00:00:00.202, GC gen0: 0, gen1: 0, gen2: 0
速度提升了775%——还不错。通过查看.NET Reflector中的代码,发现ReferenceEquals
是一个本地/非托管函数。=
运算符调用HashCompare.GenericEqualityIntrinsic<'T>
,最终到达内部函数GenericEqualityObj
。在Reflector中,这个美丽的函数反编译成 122 行 C# 代码。显然,等式是一个复杂的问题。对于null
检查,简单的引用比较已经足够,因此可以避免更微妙的相等语义产生的开销。
模式匹配也避免了等号运算符的开销。以下函数的性能与ReferenceEquals
类似,但仅适用于在F#之外定义或装饰了[<AllowNullLiteral>]
的类型。
let inline isNullMatch value = match value with null -> true | _ -> false
test isNullMatch //Real: 00:00:00.205, CPU: 00:00:00.202, GC gen0: 0, gen1: 0, gen2: 0
正如Maslow的评论中所指出的那样,F# 4.0版本中增加了一个isNull
运算符。它的定义与上面的isNullMatch
相同,因此能够实现最佳性能。
如果您不想在空情况下执行任何操作,那么可以使用单元值()
:
match value with
| null -> ()
| _ -> // your code here
当然,你也可以像在C#中一样执行空值检查,这在这种情况下可能更清晰:
if value <> null then
// your code here
如果你有一个在C#或.NET库中声明过的类型(不是在F#中声明的),那么null
就是这种类型的合适值,你可以像kvb所示那样轻松地将该值与null
进行比较。例如,假设C#调用者向你提供了Random
的实例:
let foo (arg:System.Random) =
if arg <> null then
// do something
如果C#调用方给你一个在F#中声明的类型,事情会变得更加棘手。在F#中声明的类型没有null
值,F#编译器也不允许将它们赋值为null
或检查它们是否为null
。问题在于C#不进行这种检查,因此C#调用方仍然可能会给你null
。例如:
type MyType(n:int) =
member x.Number = n
在这种情况下,你需要使用装箱(boxing)或者 Unchecked.defaultOf<_>
:
let foo (m:MyType) =
if (box m) <> null then
// do something
let foo (m:MyType) =
if m <> Unchecked.defaultOf<_> then
// do something
当然,在 F# 中通常不鼓励使用 null 值,但是...
不去深入举例子等,这里有一篇文章提供了一些 @ MSDN 例子,它具体揭示了如何检测 null 值 -- 然后你可以将其从输入中解析出来或者按照需要进行处理。
我最近遇到了一个类似的困境。我的问题是,我公开了一个使用F#开发的API,可以从C#代码中使用。C#在将接口或类传递给方法时不会出现问题,但如果方法及其参数的类型是F#类型,则无法正确执行空检查。
原因是F#类型最初不接受null
字面量,尽管CLR中没有任何东西保护您的API免受被不正确调用的影响,特别是来自更具有空值容忍性的语言(如C#)。除非使用相当丑陋的[<AllowNullLiteral>]
属性,否则您最初将无法正确地进行空值保护。
所以,我找到了相关主题中的这个解决方案,它允许我保持我的F#代码整洁和友好。通常我会创建一个函数,接受任何对象并将其转换为Option
,然后我会对None
进行验证,而不是null
。这类似于在F#中预定义的Option.ofObj
函数,但后者需要显式可空的对象(因此注释了AllowNullLiteral
)。
我找到了一个简单的方法来做这件事
open System
Object.ReferenceEquals(value, null)
isNull
,这个答案需要做哪些修改?Operators.isNull : value:'T -> bool when 'T : null
http://blogs.msdn.com/b/dotnet/archive/2015/04/29/rounding-out-visual-f-4-0-in-vs-2015-rc.aspx - Maslow