2012-05-24 96 views
12

空引用考虑以下几点:测试在F#

[<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<Tweet[]>)反序列化将导致无论是用户还是发件人字段为空(至少这就是调试器告诉我)。

如果我尝试写:

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

编译器发出错误:“类型‘TweetUser’没有‘空’作为一个适当的值”

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

+1

是否'如果tweet.User <> Unchecked.defaultof <_>'工作?如果没有,那么总是有['AllowNullLiteral'属性](http://msdn.microsoft.com/en-us/library/ee353608.aspx)。 – ildjarn

+0

Unchecked.defaultof <_>编译但在运行时无法正常工作(不正确匹配空)。 AllowNullLiteral对记录字段无效。好的建议,但是。 –

回答

17

周期性扩大@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<_>是做正确的事,并生产用于您的记录类型的空值;问题在于默认的相等运算符使用泛型结构比较,当使用F#类型时,它期望您始终按F#的规则进行游戏。无论如何,一个空检查确实只能保证参考比较。

+0

一旦你理解了这个概念,就会有意义。谢谢! –

+0

有没有使用'Unchecked.defaultof <_>'在这里,而不是仅仅'null'的优势呢?似乎后者会避免函数调用,并允许它返回结构的正确结果。 –

+0

@DaxFohl:F#类型的实例是不允许被默认实例化为'null'(见['AllowNullLiteralAttribute'](http://msdn.microsoft.com/en-us/library/ee353608.aspx))。这里使用的约束特别禁止值类型,因为它们在语义上没有意义。最后,'Unchecked.defaultof'是一个编译器内在的(如C#中的'default'),所以在那里没有函数调用。 – ildjarn

13

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

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

在你的榜样,TweetUser是(记录)型F#中声明,所以语言不允许治疗nullTweetUser类型的值。但是,仍然可以通过Reflection或C#代码获得null值,因此F#提供了一个“不安全”函数,该函数创建任何类型的值(包括F#记录),该值通常不应具有null值。这是Unchecked.defaultOf<_>功能,你可以用它来实现这样一个帮手:

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

另外,如果您标记与AllowNullLiteral属性的类型,那么你说的F#编译器,它应该允许null作为该特定类型的值,即使它是F#中声明的类型(并且它通常不会允许null)。

+0

AllowNullLiteral不允许记录字段。与Unchecked.defaultof <>比较时,访问tweet.User时出现程序错误。换句话说,只要引用tweet.User进行比较,当它为空就足以导致错误。奇怪的。 –

+1

@MikeWard属性需要被施加到型 - 你的情况'TweetUser' - 然后它指定的类型的任何发生(不只是在该领域,而且在'if'表达)可具有'null'值。 –

+1

我也试过。 AllowNullLiteral不能应用于记录类型。 reference.equals的东西按预期工作。只是我们必须忍受的那些F#互操作怪异之一。真的很喜欢整个语言。 –

1

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

isNull <| box obj 

let isMyObjNull = isNull <| box obj 

match box obj with 
| isNull -> (* obj is null *) 
| _ -> (* obj is not null *) 
相关问题