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 such:
match value with
| null -> ...
| _ -> ...
The problem I'm having is the original code is structured in C# as:
if ( value != null ) {
...
}
How do I do the equivalent in F#? Is there a no-op for pattern matching? Is there a way to check for null with an if statement?
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
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.
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 [<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
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.