Тестирование нулевой ссылки в 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[]>)
приведет к тому, что поле User или Sender будет пустым (по крайней мере, то, что говорит мне отладчик).
Если я попытаюсь написать следующее:
let name = if tweet.User <> null
then tweet.User.Name
else tweet.Sender.Name
компилятор испускает ошибку: "Тип" TweetUser "не имеет" null "в качестве правильного значения"
Как проверить нулевые значения в этом случае?
Ответы
Ответ 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<_>
делает правильную вещь и создает нули для ваших типов записей; проблема заключается в том, что оператор равенства по умолчанию использует общее структурное сравнение, которое ожидает, что вы всегда будете играть по правилам F # при использовании типов F #. В любом случае нулевая проверка действительно требует только сравнения ссылок.
Ответ 2
Чтобы добавить некоторые детали в комментарий от @ildjarn, вы получаете сообщение об ошибке, потому что F # не позволяет использовать null
как значение типов, объявленных в F #. Мотивацией для этого является то, что F # пытается исключить значения null
(и NullReferenceException
) из чистых программ F #.
Однако, если вы используете типы, которые не определены в F #, вам все же разрешено использовать null
(например, при вызове функции, которая принимает System.Random
в качестве аргумента, вы можете дать ей null
), Это необходимо для взаимодействия, потому что вам может потребоваться передать null
в библиотеку .NET или принять его в качестве результата.
В вашем примере TweetUser
- это (тип записи), объявленный в F #, поэтому язык не позволяет обрабатывать null
как значение типа TweetUser
. Тем не менее, вы все равно можете получить значение null
через ie Reflection или из кода С#, поэтому F # предоставляет "небезопасную" функцию, которая создает значение null
любого типа, включая записи F #, которые обычно не имеют значения null
, Это функция Unchecked.defaultOf<_>
, и вы можете использовать ее для реализации вспомогательного помощника:
let inline isNull x = x = Unchecked.defaultof<_>
В качестве альтернативы, если вы помечаете тип с атрибутом AllowNullLiteral
, то вы говорите компилятору F #, что он должен разрешать null
как значение для этого конкретного типа, даже если это тип, объявленный в F # (и обычно это не разрешало бы null
).
Ответ 3
Хотя этот вопрос устарел, я не видел примеров бокса для решения проблемы. В случаях, когда мой ведущий не разрешает нулевые литералы, но может быть установлен из представления, я предпочитаю использовать бокс.
isNull <| box obj
или
let isMyObjNull = isNull <| box obj
или
match box obj with
| isNull -> (* obj is null *)
| _ -> (* obj is not null *)