Gestion des valeurs Null dans F#

J'ai besoin d'interop avec du code C # avec F#. Null est une valeur possible qu'il est donné, donc j'ai besoin de vérifier si la valeur était null. Les documents suggèrent d'utiliser la correspondance de modèle en tant que telle:

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

Le problème que j'ai est que le code d'origine est structuré en C # comme:

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

Comment faire l'équivalent en F#? Est-il un no-op pour la correspondance de modèle? Existe-t-il un moyen de vérifier null avec une instruction if?

31
demandé sur Jonathan Sternberg 2011-07-25 18:42:35

5 réponses

Si vous ne voulez rien faire dans le cas null, Vous pouvez utiliser la valeur unitaire ():

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

Bien sûr, vous pouvez aussi faire la vérification null comme en C#, ce qui est probablement plus clair dans ce cas:

if value <> null then
    // your code here
32
répondu kvb 2011-07-25 14:50:56

Pour une raison quelconque (je n'ai pas encore étudié pourquoi) not (obj.ReferenceEquals(value, null)) fonctionne beaucoup mieux que value <> null. J'écris beaucoup de code F# qui est utilisé à partir de C#, donc je garde un module "interop" pour faciliter le traitement de null. En outre, si vous préférez avoir votre cas "normal" en premier lors de la correspondance de motif, vous pouvez utiliser un motif actif:

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

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

Si vous voulez une simple instruction if, cela fonctionne aussi:

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

if notNull value then
  //do something with value

Mettre à jour

Voici quelques repères et des informations supplémentaires sur la écart de performance:

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

Une accélération de 775% -- pas trop mal. Après avoir regardé le code dans. Net Reflector: ReferenceEquals est une fonction native/non gérée. Le = opérateur appels HashCompare.GenericEqualityIntrinsic<'T>, en fin de compte se terminant à la fonction interne GenericEqualityObj. En réflecteur, cette beauté décompile à 122 lignes de C#. De toute évidence, l'égalité est une question compliquée. Pour null - vérifier une comparaison de référence simple est suffisant, de sorte que vous pouvez éviter le coût de l'égalité plus subtile sémantique.

Mise à jour 2

La correspondance de modèle évite également la surcharge de l'opérateur d'égalité. La fonction suivante fonctionne de la même manière que ReferenceEquals, mais ne fonctionne qu'avec les types définis en dehors de F# ou décorés avec [<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

Mise à jour 3

Comme indiqué dans le commentaire de Maslow, un isNull l'opérateur a été ajouté en F # 4.0. Il est défini de la même manière que isNullMatch ci-dessus, et fonctionne donc de manière optimale.

48
répondu Daniel 2017-05-23 12:24:41

Si vous avez un type qui a été déclaré en C# ou dans une bibliothèque. net en général (pas en F#) alors null est une valeur appropriée de ce type et vous pouvez facilement comparer la valeur à null comme posté par kvb. Par exemple, supposons que c # caller vous donne une instance de Random:

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

Les choses deviennent plus délicates si l'appelant C# Vous donne un type qui a été déclaré En F#. Les Types déclarés dans F# n'ont pas null comme valeur et le compilateur F# Ne vous permettra pas de les affecter null ou de vérifier contre null. Le problème est que C# ne fait pas cette vérification et un appelant C# pourrait toujours vous donner null. Par exemple:

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

Dans ce cas, vous avez besoin de boxe ou Unchecked.defaultOf<_>:

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

let foo (m:MyType) =
  if m <> Unchecked.defaultOf<_> then
    // do something
14
répondu Tomas Petricek 2011-07-25 14:54:56

Bien sûr, les valeurs NULL sont généralement déconseillées dans F#, mais...

Sans entrer dans les exemples etc il y a un article avec quelques exemples @ MSDN ici. Il révèle spécifiquement comment détecter un null-et vous pouvez ensuite l'analyser hors de l'entrée ou gérer comme vous le souhaitez.

0
répondu Hardryv 2011-07-25 14:48:09

J'ai récemment fait face à un dilemme similaire . Mon problème était que j'ai exposé une API développée par F#qui pourrait être consommée à partir du code C#. C# n'a aucun problème à passer null à une méthode qui accepte les interfaces ou les classes, mais si la méthode et les types de ses arguments sont des types F#, vous n'êtes pas autorisé à effectuer correctement les vérifications null.

La raison en est que les types F# n'acceptent initialement pas le littéral null, alors que techniquement rien dans le CLR ne protège votre API contre les erreurs invoqué, en particulier à partir d'un langage plus tolérant à null tel que C#. Vous finiriez par être incapable de vous protéger correctement à moins d'utiliser l'attribut [<AllowNullLiteral>], ce qui est une approche assez laide.

Donc, je suis venu à Cette solution dans le sujet connexe , ce qui me permet de garder mon code F# propre et F#-friendly. En général, je crée une fonction qui acceptera n'importe quel objet et le convertira en Option, puis au lieu de null Je validerai contre None. C'est similaire à l'utilisation de la fonction prédéfinie dans F # Option.ofObj, mais cette dernière nécessite que l'objet passé soit explicitement nullable (ainsi annoté avec la laideur AllowNullLiteral).

0
répondu Ivaylo Slavov 2017-06-07 09:07:10