2012-05-24 9 views
12

Dato il seguente:Test per riferimento null in 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} 

deserializzazione con DataContractJsonSerializer(typeof<Tweet[]>) comporterà l'utente o campo Mittente essere null (almeno questo è quello che il debugger mi sta dicendo).

Se provo a scrivere il seguente:

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

il compilatore emette l'errore: "Il tipo di 'TweetUser' non ha 'null' come un valore corretto"

Come faccio a testare valori null in questo caso?

+1

Vuol 'se tweet.User <> Unchecked.defaultof <_>' funzionano? In caso contrario, c'è sempre l'attributo ['AllowNullLiteral'] (http://msdn.microsoft.com/en-us/library/ee353608.aspx). – ildjarn

+0

Unchecked.defaultof <_> compila ma non funziona in fase di esecuzione (non corrisponde correttamente null). AllowNullLiteral non è valido per un campo record. Buoni suggerimenti comunque. –

risposta

17

Per espandere ciclicamente sulla risposta @Tomas; -]

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

o

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

Unchecked.defaultof<_> sta facendo la cosa giusta e la produzione di valori nulli per i vostri tipi di record; il problema è che l'operatore di uguaglianza di default usa un confronto strutturale generico, che si aspetta di giocare sempre con le regole di F # quando si usano i tipi F #. In ogni caso, un null-check garantisce solo il confronto referenziale in primo luogo.

+0

Ha senso una volta compreso il concetto. Grazie! –

+0

C'è un vantaggio di utilizzare '' Unchecked.defaultof <_> qui piuttosto che solo 'null'? Sembra che quest'ultimo eviti una chiamata di funzione, oltre a consentire di restituire il risultato corretto per le strutture. –

+0

@DaxFohl: Le istanze di F # tipi non possono essere istanziati come 'null' di default (vedi [' AllowNullLiteralAttribute'] (http://msdn.microsoft.com/en-us/library/ee353608.aspx)). I vincoli qui usati proibiscono specificatamente i tipi di valore, poiché qui non hanno senso semanticamente. Infine, 'Unchecked.defaultof' è un compilatore intrinseco (come' default' in C#) quindi non ci sono chiamate di funzioni lì. – ildjarn

13

Per aggiungere alcuni dettagli al commento di @ildjarn, viene visualizzato il messaggio di errore, poiché F # non consente l'utilizzo di null come valore dei tipi dichiarati in F #. La motivazione è che F # cerca di eliminare i valori null (e NullReferenceException) da programmi F # puri.

Tuttavia, se si sta utilizzando i tipi che non sono definiti in F #, si sta ancora permesso usare null (ad esempio quando si chiama una funzione che prende System.Random come argomento, si può dare null). Questo è necessario per l'interoperabilità, perché potrebbe essere necessario passare null a una libreria .NET o accettarlo come risultato.

Nel tuo esempio, è un TweetUser (record) tipo dichiarato in F #, quindi la lingua non consente il trattamento di null come un valore di tipo TweetUser. Tuttavia, è possibile ottenere il valore null tramite i.e. Reflection o dal codice C#, quindi F # fornisce una funzione "non sicura" che crea un valore null di qualsiasi tipo, inclusi i record F #, che normalmente non dovrebbero avere il valore null. Questa è la funzione Unchecked.defaultOf<_> e lo si può utilizzare per implementare un aiuto come questo:

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

In alternativa, se si contrassegna un tipo con l'attributo AllowNullLiteral, allora si sta dicendo al compilatore F # che dovrebbe consentire null come valore per quel tipo specifico, anche se è un tipo dichiarato in F # (e non consentirebbe normalmente null).

+0

AllowNullLiteral non consentito nei campi record. Errore di programma che accede a tweet.User quando si confronta con Unchecked.defaultof <>. In altre parole, basta fare riferimento a tweet.User per fare il confronto quando è nullo è sufficiente causare un errore. Strano. –

+1

@MikeWard deve essere applicato al tipo L'attributo - nel tuo caso 'TweetUser' - allora precisa che qualsiasi occorrenza del tipo (non solo in campo, ma anche nel' if' espressione) può avere 'null' valore. –

+1

Ho provato anche quello. AllowNullLiteral non può essere applicato ai tipi di record. La cosa reference.equals funziona come previsto. Solo una di quelle stranezze di F # in cui dovremo convivere. Mi piace molto il langauge nel complesso. –

1

Anche se questa domanda è vecchia, non ho visto alcun esempio di boxe per risolvere il problema. Nei casi in cui il mio relatore non consente valori letterali nulli, ma può essere impostato da una vista, preferisco usare la boxe.

isNull <| box obj 

o

let isMyObjNull = isNull <| box obj 

o

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