Обработка нулевых значений в F #
Мне нужно взаимодействовать с некоторым кодом С# с F #. Null - это возможное значение, которое оно задано, поэтому мне нужно проверить, было ли значение null. Документы предлагают использовать сопоставление шаблонов как таковых:
match value with
| null -> ...
| _ -> ...
Проблема, с которой я столкнулась, это исходный код, структурированный в С# как:
if ( value != null ) {
...
}
Как сделать эквивалент в F #? Нет ли метода для сопоставления шаблонов? Есть ли способ проверить значение null с помощью оператора if?
Ответы
Ответ 1
Если вы не хотите ничего делать в нулевом случае, вы можете использовать значение единицы ()
:
match value with
| null -> ()
| _ -> // your code here
Конечно, вы также можете выполнить нулевую проверку, как в С#, которая, вероятно, более ясна в этом случае:
if value <> null then
// your code here
Ответ 2
По какой-то причине (я еще не исследовал почему) not (obj.ReferenceEquals(value, null))
работает намного лучше, чем value <> null
. Я пишу много кода на F #, который используется из С#, поэтому я держу модуль "взаимодействия", чтобы облегчить работу с 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
ОБНОВИТЬ
Вот некоторые тесты и дополнительная информация о несоответствии производительности:
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 строк С#. Очевидно, что равенство является сложной проблемой. Для null
-checking достаточно простого эталонного сравнения, поэтому вы можете избежать затрат на семантику более тонкого равенства.
ОБНОВЛЕНИЕ 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
выше, и, следовательно, работает оптимально.
Ответ 3
Если у вас есть тип, который был объявлен в С# или .NET-библиотеке в целом (не в F #), тогда null
является правильным значением этого типа, и вы можете легко сравнить значение с null
как опубликовано по kvb. Например, предположим, что вызывающий С# дает вам экземпляр Random
:
let foo (arg:System.Random) =
if arg <> null then
// do something
Все становится сложнее, если вызывающий С# дает вам тип, объявленный в F #. Типы, объявленные в F #, не имеют null
в качестве значения, а компилятор F # не позволяет вам назначать их null
или проверять их на null
. Проблема в том, что С# не выполняет эту проверку, а вызывающий С# все еще может дать вам 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
Ответ 4
Конечно, нулевые значения обычно не приветствуются в F #, но...
Не вдаваясь в примеры и т.д., есть статья с некоторыми примерами @MSDN здесь. Он показывает, в частности, как определить нуль - и вы можете затем проанализировать его из ввода или дескриптора по желанию.
Ответ 5
Недавно я столкнулся с аналогичной дилеммой . Моя проблема заключалась в том, что я раскрыл API-интерфейс F #, который можно было бы использовать из кода С#. С# не имеет проблемы с передачей null
методу, который принимает интерфейсы или классы, но если метод и типы его аргументов являются типами F #, вам не разрешается корректно выполнять нулевые проверки.
Причина в том, что типы F # изначально не принимают литерал null
, в то время как технически ничего в среде CLR не защищает ваш API от неправильного вызова, особенно с более нулеустойчивого языка, такого как С#. Вначале вы оказались бы неспособными правильно обнулить защиту, если не используете атрибут [<AllowNullLiteral>]
, что довольно уродливое.
Итак, я пришел к этому решению в соответствующем разделе, что позволяет мне сохранять мой код F # чистым и дружественным F #. В общем, я создаю функцию, которая примет любой объект и преобразует ее в Option
, а вместо null
проверяет на None
. Это похоже на использование предварительно определенной функции F # Option.ofObj
, но последнее требует, чтобы переданный объект был явно обнуляемым (таким образом, аннотированный с AllowNullLiteral
уродством).
Ответ 6
Я нашел простой способ сделать это
open System
Object.ReferenceEquals(value, null)