处理F#中的空值

Jon*_*erg 34 c# null f# interop

我需要使用F#与一些C#代码互操作.Null是一个可能的值,因此我需要检查该值是否为null.文档建议使用模式匹配:

match value with
| null -> ...
| _ -> ...
Run Code Online (Sandbox Code Playgroud)

我遇到的问题是原始代码是用C#编写的:

if ( value != null ) {
    ...
}
Run Code Online (Sandbox Code Playgroud)

如何在F#中进行等效操作?模式匹配是否存在无操作?有没有办法用if语句检查null?

Dan*_*iel 52

出于某种原因(我还没有调查过为什么)not (obj.ReferenceEquals(value, null))表现要好得多value <> null.我编写了许多从C#中使用的F#代码,因此我保留了一个"互操作"模块以便于处理null.此外,如果您希望在模式匹配时首先使用"正常"情况,则可以使用活动模式:

let (|NotNull|_|) value = 
  if obj.ReferenceEquals(value, null) then None 
  else Some()

match value with
| NotNull ->
  //do something with value
| _ -> nullArg "value"
Run Code Online (Sandbox Code Playgroud)

如果你想要一个简单的if陈述,这也适用:

let inline notNull value = not (obj.ReferenceEquals(value, null))

if notNull value then
  //do something with value
Run Code Online (Sandbox Code Playgroud)

UPDATE

以下是有关性能差异的一些基准测试和其他信息:

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
Run Code Online (Sandbox Code Playgroud)

加速775% - 还不错.在查看.NET Reflector中的代码之后:ReferenceEquals是一个本机/非托管函数.该=运营商的呼叫HashCompare.GenericEqualityIntrinsic<'T>,最终在内部函数结束了GenericEqualityObj.在Reflector中,这个美容反编译为122行C#.显然,平等是一个复杂的问题.对于 - null检查简单的引用比较就足够了,因此可以避免更微妙的等式语义的代价.

更新2

模式匹配还避免了相等运算符的开销.以下函数执行类似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
Run Code Online (Sandbox Code Playgroud)

更新3

正如马斯洛的评论所述,F#4.0中增加了一名isNull运营商.它的定义与isNullMatch上面相同,因此表现最佳.

  • 根据F#4中新的`isNull`,这个答案需要改变什么?`Operators.isNull:value:'T - > bool当'T:null`http://blogs.msdn.com/b/dotnet/archive/2015/04/29/rounding-out-visual-f-4- 0-在-VS-2015-rc.aspx (3认同)

kvb*_*kvb 36

如果您不想在null情况下执行任何操作,则可以使用单位值():

match value with
| null -> ()
| _ -> // your code here
Run Code Online (Sandbox Code Playgroud)

当然,您也可以像在C#中一样进行空检查,在这种情况下可能更清楚:

if value <> null then
    // your code here
Run Code Online (Sandbox Code Playgroud)


Tom*_*cek 16

如果您的类型已经在C#或.NET库中声明(不是在F#中),那么这null是该类型的正确值,您可以轻松地将该值与nullkvb发布的值进行比较.例如,假设C#调用者为您提供了以下实例Random:

let foo (arg:System.Random) =
  if arg <> null then 
    // do something
Run Code Online (Sandbox Code Playgroud)

如果C#调用者给你一个在F#中声明的类型,事情会变得更加棘手.在F#中声明的类型没有null作为值,F#编译器将不允许您分配它们null或检查它们null.问题是C#没有做这个检查,C#调用者仍然可以给你null.例如:

type MyType(n:int) =
  member x.Number = n
Run Code Online (Sandbox Code Playgroud)

在这种情况下,你需要拳击或Unchecked.defaultOf<_>:

let foo (m:MyType) =
  if (box m) <> null then
    // do something

let foo (m:MyType) =
  if m <> Unchecked.defaultOf<_> then
    // do something
Run Code Online (Sandbox Code Playgroud)