2012-05-24 16 views
12

Biorąc pod uwagę następujące elementy:Testowanie zerowy odniesienia w 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} 

deserializacji z DataContractJsonSerializer(typeof<Tweet[]>) spowoduje użytkownika albo polu nadawcy jest zerowa (przynajmniej to co debugger mówi mi).

Gdy próbuję napisać następujące:

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

kompilator emituje ten błąd: „Typ«TweetUser»nie ma«null»jako właściwą wartość”

Jak przetestować wartości null w tym przypadku?

+1

Czy 'jeśli tweet.User <> Unchecked.defaultof <_>' działa? Jeśli nie, to zawsze jest atrybut ['AllowNullLiteral'] (http://msdn.microsoft.com/en-us/library/ee353608.aspx). – ildjarn

+0

Niesprawdzony.defaultof <_> kompiluje, ale nie działa w środowisku wykonawczym (nie pasuje poprawnie do wartości null). AllowNullLiteral nie jest poprawne dla pola rekordu. Dobre sugestie jednak. –

Odpowiedz

17

Aby cyklicznie rozwinąć odpowiedź @Tomas; -]

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

lub

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

Unchecked.defaultof<_> robi słusznie i produkcji null dla swoich typów rekordów; Problem polega na tym, że domyślny operator równości używa generycznego porównania strukturalnego, które oczekuje, że zawsze będziesz grał według reguł F #, gdy używasz typów F #. W każdym razie kontrola zerowa rzeczywiście tylko gwarantuje porównanie referencyjne w pierwszej kolejności.

+0

Ma sens, gdy zrozumiesz pojęcie. Dzięki! –

+0

Czy istnieje korzyść z używania 'Unchecked.defaultof <_>' tutaj zamiast tylko 'null'? Wygląda na to, że ten drugi uniknie wywołania funkcji, a także pozwoli mu zwrócić poprawny wynik dla struktur. –

+0

@DaxFohl: Instancje typów F # nie mogą być tworzone jako 'null' domyślnie (zobacz [' AllowNullLiteralAttribute'] (http://msdn.microsoft.com/en-us/library/ee353608.aspx)). Ograniczenia tutaj użyte wyraźnie zabraniają typów wartości, ponieważ nie mają sensu tu semantycznie. Wreszcie, 'Unchecked.defaultof' jest wewnętrznym kompilatorem (jak' default' w C#), więc nie ma tam wywołania funkcji. – ildjarn

13

Aby dodać niektóre szczegóły do ​​komentarza przez @ildjarn, otrzymujesz komunikat o błędzie, ponieważ F # nie zezwala na używanie null jako wartości typów zadeklarowanych w F #. Motywacją tego jest fakt, że F # próbuje wyeliminować wartości (i NullReferenceException) z czystych programów F #.

Jednakże, jeśli używasz typów, które nie są zdefiniowane w F #, nadal mogą korzystać null (np podczas wywoływania funkcji, która pobiera System.Random jako argument można podać go null). Jest to potrzebne do zapewnienia interoperacyjności, ponieważ może być konieczne przekazanie null do biblioteki .NET lub zaakceptowanie jej jako wynik.

W przykładzie TweetUser jest (zapis) Typ zadeklarowane w F #, więc język nie pozwala na traktowanie null jako wartość typu TweetUser. Jednak nadal można uzyskać wartość null za pośrednictwem Reflection lub z kodu C#, więc F # zapewnia "niebezpieczną" funkcję, która tworzy wartość dowolnego typu - włącznie z rekordami F #, które normalnie nie powinny mieć wartości null. Jest to funkcja Unchecked.defaultOf<_> i można go używać do wdrożenia pomocnika takiego:

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

Ewentualnie, jeśli oznaczyć typu z atrybutem AllowNullLiteral, a potem mówisz do F # kompilator, że powinien on umożliwić null jako wartość dla tego konkretnego typu, nawet jeśli jest to typ zadeklarowany w F # (i normalnie nie pozwoliłby na to null).

+0

AllowNullLiteral nie jest dozwolone w polach rekordów. Błędy programowe uzyskujące dostęp do tweet.User podczas porównywania z Unchecked.default <>. Innymi słowy, wystarczy odwołanie się do tweet.User do porównania, gdy jest zerowy, wystarczy, aby spowodować błąd. Dziwne. –

+1

@MikeWard Atrybut musi zostać zastosowany do typu - w twoim przypadku "TweetUser" - wtedy określa, że ​​każde wystąpienie typu (nie tylko w polu, ale także w wyrażeniu "if") może mieć wartość "null" wartość. –

+1

Próbowałem również. ZezwolenieNullLiteral nie może być zastosowane do typów rekordów. Element reference.equals działa zgodnie z oczekiwaniami. Tylko jedna z tych dziwnych osobowości, z którymi będziemy musieli żyć. Naprawdę lubię ogólny język. –

1

Chociaż to pytanie jest stare, nie widziałem żadnych przykładów boksu, aby rozwiązać problem. W przypadkach, w których mój prezenter nie dopuszcza literałów zerowych, ale może być ustawiony z widoku, wolę używać boksu.

isNull <| box obj 

lub

let isMyObjNull = isNull <| box obj 

lub

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