【问题标题】:Testing for null reference in F#在 F# 中测试空引用
【发布时间】:2012-05-24 23:19:46
【问题描述】:

鉴于以下情况:

[<DataContract>]
type TweetUser = {
    [<field:DataMember(Name="followers_count")>] Followers:int
    [<field:DataMember(Name="screen_name")>] Name:string
    [<field:DataMember(Name="id_str")>] Id:int
    [<field:DataMember(Name="location")>] Location:string}

[<DataContract>]
type Tweet = {
    [<field:DataMember(Name="id_str")>] Id:string
    [<field:DataMember(Name="text")>] Text:string
    [<field:DataMember(Name="retweeted")>] IsRetweeted:bool
    [<field:DataMember(Name="created_at")>] DateStr:string
    [<field:DataMember(Name="user", IsRequired=false)>] User:TweetUser
    [<field:DataMember(Name="sender", IsRequired=false)>] Sender:TweetUser
    [<field:DataMember(Name="source")>] Source:string}

使用 DataContractJsonSerializer(typeof&lt;Tweet[]&gt;) 反序列化将导致 User 或 Sender 字段为空(至少调试器告诉我的是这样)。

如果我尝试编写以下内容:

    let name = if tweet.User <> null 
                  then tweet.User.Name
                  else tweet.Sender.Name

编译器发出错误:“类型'TweetUser'没有'null'作为正确值”

在这种情况下如何测试空值?

【问题讨论】:

  • if tweet.User &lt;&gt; Unchecked.defaultof&lt;_&gt; 有效吗?如果没有,那么总会有AllowNullLiteral attribute
  • Unchecked.defaultof<_> 编译但在运行时不起作用(与 null 不正确匹配)。 AllowNullLiteral 对记录字段无效。不过建议很好。

标签: f#


【解决方案1】:

循环扩展@Tomas 的回答;-]

let name = if not <| obj.ReferenceEquals (tweet.User, null)
              then tweet.User.Name
              else tweet.Sender.Name

let inline isNull (x:^T when ^T : not struct) = obj.ReferenceEquals (x, null)

Unchecked.defaultof&lt;_&gt; 正在做正确的事情并为您的记录类型生成空值;问题是默认的相等运算符使用通用结构比较,它希望您在使用 F# 类型时始终遵守 F# 的规则。无论如何,空检查实际上只需要首先进行引用比较。

【讨论】:

  • 一旦你理解了这个概念就有意义了。谢谢!
  • 在这里使用Unchecked.defaultof&lt;_&gt; 而不仅仅是null 有什么好处吗?似乎后者会避免函数调用,并允许它为结构返回正确的结果。
  • @DaxFohl :默认情况下,不允许将 F# 类型的实例实例化为 null(参见 AllowNullLiteralAttribute)。这里使用的约束特别禁止值类型,因为它们在语义上没有意义。最后,Unchecked.defaultof 是编译器内在函数(就像 C# 中的 default),所以那里没有函数调用。
  • @DaxFohl :后来发现,我对Unchecked.defaultof&lt;_&gt; 的调用具有误导性,因为它总是转换为Unchecked.defaultof&lt;obj&gt;,所以null 在这里毕竟是合适的。
【解决方案2】:

要向@ildjarn 的注释添加一些详细信息,您会收到错误消息,因为 F# 不允许使用 null 作为在 F# 中声明的类型的值。这样做的动机是 F# 试图从纯 F# 程序中消除 null 值(和 NullReferenceException)。

但是,如果您使用 F# 中未定义的类型,您仍然可以使用 null(例如,当调用以 System.Random 作为参数的函数时,您可以将其指定为 null) .这是互操作性所必需的,因为您可能需要将 null 传递给 .NET 库或接受它作为结果。

在您的示例中,TweetUser 是在 F# 中声明的(记录)类型,因此该语言不允许将 null 视为 TweetUser 类型的值。但是,您仍然可以通过即反射或从 C# 代码获取 null 值,因此 F# 提供了一个“不安全”函数,该函数创建任何类型的 null 值 - 包括 F# 记录,通常不应具有 null 值.这是Unchecked.defaultOf&lt;_&gt; 函数,您可以使用它来实现这样的助手:

let inline isNull x = x = Unchecked.defaultof<_>

或者,如果您使用AllowNullLiteral 属性标记一个类型,那么您就是在告诉F# 编译器它应该允许null 作为该特定类型的值,即使它是在F# 中声明的类型(而且它通常不允许null)。

【讨论】:

  • AllowNullLiteral 不允许在记录字段中使用。与 Unchecked.defaultof 进行比较时,访问 tweet.User 的程序出错。换句话说,仅仅引用 tweet.User 在它为空时进行比较就足以导致错误。很奇怪。
  • @MikeWard 该属性需要应用于类型-在您的情况下为TweetUser-然后它指定该类型的任何出现(不仅在字段中,而且在if表达式)可以有null值。
  • 我也试过了。 AllowNullLiteral 不能应用于记录类型。 reference.equals 事情按预期工作。只是我们必须忍受的那些 F# 互操作怪事之一。真的很喜欢整体语言。
  • 建议的isNull 函数似乎不起作用...如果您调用Unchecked.defaultof&lt;Foo&gt; |&gt; isNull,结果是NullReferenceException。
【解决方案3】:

虽然这个问题很老,但我没有看到任何拳击的例子来解决这个问题。如果我的演示者不允许 null 文字,但可以从视图中设置,我更喜欢使用装箱。

isNull <| box obj

let isMyObjNull = isNull <| box obj

match box obj with
| null -> (* obj is null *)
| _ -> (* obj is not null *)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-09
    • 2021-12-09
    • 1970-01-01
    • 2014-02-24
    • 1970-01-01
    • 2017-06-13
    相关资源
    最近更新 更多