Обработка нулевых значений в F # - PullRequest
34 голосов
/ 25 июля 2011

Мне нужно взаимодействовать с кодом C # с помощью F #. Нулевое значение - это возможное значение, которое ему дается, поэтому мне нужно проверить, было ли это значение нулевым. Документы предлагают использовать сопоставление с образцом как таковое:

match value with
| null -> ...
| _ -> ...

Проблема, с которой я столкнулся, состоит в том, что оригинальный код структурирован в C # как:

if ( value != null ) {
    ...
}

Как мне сделать эквивалент в F #? Есть ли запрет на сопоставление с образцом? Есть ли способ проверить нулевое значение с помощью оператора if?

Ответы [ 6 ]

52 голосов
/ 25 июля 2011

По какой-то причине (я еще не выяснил почему) not (obj.ReferenceEquals(value, null)) работает намного лучше, чем value <> null. Я пишу много кода F #, который используется из C #, поэтому я держу модуль «взаимодействия» , чтобы облегчить работу с null. Кроме того, если вы предпочитаете сначала «нормальный» случай при сопоставлении с образцом, вы можете использовать активный шаблон:

let (|NotNull|_|) value = 
  if obj.ReferenceEquals(value, null) then None 
  else Some()

match value with
| NotNull ->
  //do something with value
| _ -> nullArg "value"

Если вам нужно простое выражение if, это тоже работает:

let inline notNull value = not (obj.ReferenceEquals(value, null))

if notNull value then
  //do something with value

UPDATE

Вот некоторые тесты и дополнительная информация о несоответствии производительности:

let inline isNull value = (value = null)
let inline isNullFast value = obj.ReferenceEquals(value, null)
let items = List.init 10000000 (fun _ -> null:obj)
let test f = items |> Seq.forall f |> printfn "%b"

#time "on"
test isNull     //Real: 00:00:01.512, CPU: 00:00:01.513, GC gen0: 0, gen1: 0, gen2: 0
test isNullFast //Real: 00:00:00.195, CPU: 00:00:00.202, GC gen0: 0, gen1: 0, gen2: 0

Ускорение на 775% - неплохо. После просмотра кода в .NET Reflector: ReferenceEquals является нативной / неуправляемой функцией. Оператор = вызывает HashCompare.GenericEqualityIntrinsic<'T>, в конечном итоге заканчивая внутренней функцией GenericEqualityObj. В Reflector эта красота декомпилируется до 122 строк C #. Очевидно, что равенство является сложной проблемой. Для проверки null достаточно простого сравнения ссылок, поэтому вы можете избежать затрат на семантику более тонкого равенства.

ОБНОВЛЕНИЕ 2

Сопоставление с образцом также позволяет избежать накладных расходов на оператор равенства. Следующая функция работает аналогично ReferenceEquals, но работает только с типами, определенными вне F # или украшенными [<AllowNullLiteral>].

let inline isNullMatch value = match value with null -> true | _ -> false

test isNullMatch //Real: 00:00:00.205, CPU: 00:00:00.202, GC gen0: 0, gen1: 0, gen2: 0

ОБНОВЛЕНИЕ 3

Как отмечается в комментарии Маслоу, в F # 4.0 добавлен оператор isNull . Он определен так же, как isNullMatch выше, и, следовательно, работает оптимально.

34 голосов
/ 25 июля 2011

Если вы не хотите ничего делать в нулевом регистре, вы можете использовать значение единицы измерения ():

match value with
| null -> ()
| _ -> // your code here

Конечно, вы также можете выполнить проверку нуля, как вC #, что, вероятно, более понятно в этом случае:

if value <> null then
    // your code here
16 голосов
/ 25 июля 2011

Если у вас есть тип, который был объявлен в C # или библиотеке .NET в целом (не в F #), тогда null - это правильное значение этого типа, и вы можете легко сравнить это значение с null, как опубликованопо квб.Например, предположим, что вызывающая сторона C # дает вам экземпляр Random:

let foo (arg:System.Random) =
  if arg <> null then 
    // do something

Все становится сложнее, если вызывающая сторона C # дает вам тип, который был объявлен в F #.Типы, объявленные в F #, не имеют null в качестве значения, и компилятор F # не позволит вам присвоить их null или проверить их по null.Проблема в том, что C # не выполняет эту проверку, и вызывающий C # все равно может дать вам null.Например:

type MyType(n:int) =
  member x.Number = n

В этом случае вам необходим либо бокс, либо Unchecked.defaultOf<_>:

let foo (m:MyType) =
  if (box m) <> null then
    // do something

let foo (m:MyType) =
  if m <> Unchecked.defaultOf<_> then
    // do something
0 голосов
/ 21 мая 2019

Я нашел простой способ сделать это

open System

Object.ReferenceEquals(value, null)
0 голосов
/ 07 июня 2017

Я недавно столкнулся с подобной дилеммой . Моя проблема заключалась в том, что я представил API, разработанный на F #, который можно использовать из кода C #. В C # нет проблем с передачей null методу, который принимает интерфейсы или классы, но если метод и типы его аргументов относятся к типу F #, вам не разрешается правильно выполнять нулевые проверки.

Причина в том, что типы F # изначально не принимают литерал null, хотя технически ничто в CLR не защищает ваш API от неправильного вызова, особенно от более терпимого к нулю языка, такого как C #. Сначала вы не сможете должным образом защитить себя от нуля, если не используете атрибут [<AllowNullLiteral>], что является довольно уродливым подходом.

Итак, я пришел к этому решению в соответствующей теме , которое позволяет мне сохранять мой код F # чистым и F # дружественным. В общем, я создаю функцию, которая будет принимать любой объект и преобразует его в Option, тогда вместо null я проверю на None. Это похоже на использование предопределенной в F # Option.ofObj функции, но последняя требует, чтобы передаваемый объект был явно обнуляемым (таким образом, аннотированным AllowNullLiteral безобразием).

0 голосов
/ 25 июля 2011

Конечно, нулевые значения обычно не рекомендуются в F #, но ...

Не вдаваясь в примеры и т.д., есть статья с некоторыми примерами @ MSDN здесь .В нем подробно рассказывается, как обнаружить ноль - и вы можете затем проанализировать его из ввода или обработать по своему усмотрению.

...