Подтвердить что ты не робот

Обработка нулевых значений в F #

Мне нужно взаимодействовать с некоторым кодом С# с F #. Null - это возможное значение, которое оно задано, поэтому мне нужно проверить, было ли значение null. Документы предлагают использовать сопоставление шаблонов как таковых:

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

Проблема, с которой я столкнулась, это исходный код, структурированный в С# как:

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

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

4b9b3361

Ответ 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)