Handling Null Values in F#

前端 未结 6 879
不思量自难忘°
不思量自难忘° 2020-12-13 00:25

I need to interop with some C# code with F#. Null is a possible value that it is given so I need to check if the value was null. The docs suggest using pattern matching as s

6条回答
  •  -上瘾入骨i
    2020-12-13 01:11

    For some reason (I haven't yet investigated why) not (obj.ReferenceEquals(value, null)) performs much better than value <> null. I write a lot of F# code that is used from C#, so I keep an "interop" module around to ease dealing with null. Also, if you'd rather have your "normal" case first when pattern matching, you can use an active pattern:

    let (|NotNull|_|) value = 
      if obj.ReferenceEquals(value, null) then None 
      else Some()
    
    match value with
    | NotNull ->
      //do something with value
    | _ -> nullArg "value"
    

    If you want a simple if statement, this works too:

    let inline notNull value = not (obj.ReferenceEquals(value, null))
    
    if notNull value then
      //do something with value
    

    UPDATE

    Here are some benchmarks and additional information on the performance discrepancy:

    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
    

    A speed-up of 775% -- not too bad. After looking at the code in .NET Reflector: ReferenceEquals is a native/unmanaged function. The = operator calls HashCompare.GenericEqualityIntrinsic<'T>, ultimately ending up at the internal function GenericEqualityObj. In Reflector, this beauty decompiles to 122 lines of C#. Obviously, equality is a complicated issue. For null-checking a simple reference comparison is enough, so you can avoid the cost of subtler equality semantics.

    UPDATE 2

    Pattern matching also avoids the overhead of the equality operator. The following function performs similarly to ReferenceEquals, but only works with types defined outside F# or decorated with [].

    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
    

    UPDATE 3

    As noted in Maslow's comment, an isNull operator was added in F# 4.0. It's defined the same as isNullMatch above, and therefore performs optimally.

提交回复
热议问题