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?
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
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.
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
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.
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
).