2012-05-24 47 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 *) 
相關問題