【问题标题】:Handling Null Values in F#在 F# 中处理空值
【发布时间】:2011-07-25 14:42:35
【问题描述】:

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

match value with
| null -> ...
| _ -> ...

我遇到的问题是原始代码在 C# 中的结构如下:

if ( value != null ) {
    ...
}

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

【问题讨论】:

    标签: c# f# interop null


    【解决方案1】:

    出于某种原因(我还没有调查原因)not (obj.ReferenceEquals(value, null)) 的性能比value <> null 好得多。我写了很多从 C# 中使用的 F# 代码,所以我保留了一个 "interop" module 以方便处理 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-检查一个简单的引用比较就足够了,因此您可以避免更微妙的相等语义的成本。

    更新 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
    

    更新 3

    正如 Maslow 的评论中所指出的,isNull operator 是在 F# 4.0 中添加的。它的定义与上面的isNullMatch 相同,因此性能最佳。

    【讨论】:

    【解决方案2】:

    如果你不想在null情况下做任何事情,那么你可以使用单位值()

    match value with
    | null -> ()
    | _ -> // your code here
    

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

    if value <> null then
        // your code here
    

    【讨论】:

    【解决方案3】:

    如果您的类型已在 C# 或一般的 .NET 库中声明(不在 F# 中),则 null 是该类型的正确值,您可以轻松地将该值与发布的 null 进行比较通过 kvb。例如,假设 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
    

    在这种情况下,您需要拳击或Unchecked.defaultOf&lt;_&gt;

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

    【讨论】:

      【解决方案4】:

      当然,在 F# 中通常不鼓励使用空值,但是...

      在不涉及示例等的情况下,有一篇带有一些示例的文章@MSDN here。它具体揭示了如何检测空值 - 然后您可以根据需要将其从输入或句柄中解析出来。

      【讨论】:

      • 问题是现实生活中经常会给你带来讨厌的 C# 类链,所以你必须做一些丑陋的事情,比如“if (vc null && vc.NavigationController null && vc.NavigationController.导航栏 空)”。痛苦的并不是一个 null 本身。
      【解决方案5】:

      我最近遇到了similar dilemma。我的问题是我公开了一个 F# 开发的 API,可以从 C# 代码中使用。 C# 将 null 传递给接受接口或类的方法没有问题,但如果方法及其参数的类型是 F# 类型,则不允许正确执行 null 检查。

      原因是 F# 类型最初不接受 null 文字,而从技术上讲,CLR 中的任何内容都不能保护您的 API 不被不正确地调用,尤其是从更容空的语言(例如 C#)中。除非您使用 [&lt;AllowNullLiteral&gt;] 属性,否则您最初将无法正确地保护自己,这是一种非常丑陋的方法。

      所以,我来到了this solution in the related topic,它使我的 F# 代码保持干净和 F# 友好。一般来说,我创建一个函数,它将接受任何对象并将其转换为Option,然后我将验证None 而不是null。这类似于使用 F# 中预定义的 Option.ofObj 函数,但后者确实要求传递的对象显式可为空(因此使用 AllowNullLiteral 丑陋注释)。

      【讨论】:

        【解决方案6】:

        我找到了一个简单的方法来做到这一点

        open System
        
        Object.ReferenceEquals(value, null)
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-12-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-01-15
          • 1970-01-01
          相关资源
          最近更新 更多