Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
188 views
in Technique[技术] by (71.8m points)

c# - Handling Null Values in F#

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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 [<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

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...