J'ai besoin d'interopérer avec du code C # avec F #. Null est une valeur possible qui est donnée, je dois donc vérifier si la valeur était null. Les docs suggèrent d'utiliser la correspondance de modèle en tant que telle:
match value with
| null -> ...
| _ -> ...
Le problème que je rencontre est que le code d'origine est structuré en C # comme suit:
if ( value != null ) {
...
}
Comment faire l'équivalent en F #? Existe-t-il un no-op pour le filtrage? Existe-t-il un moyen de vérifier la valeur null avec une instruction if?
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 également effectuer la vérification de 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 cherché à savoir pourquoi), not (obj.ReferenceEquals(value, null))
fonctionne beaucoup mieux que value <> null
. J'écris beaucoup de code F # utilisé en C #, donc je garde un module "interop" around pour faciliter le traitement de null
. De plus, si vous préférez que votre cas "normal" apparaisse en premier lors de la recherche 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
Voici quelques points de repère et des informations supplémentaires sur l’é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 examiné le code dans .NET Reflector: ReferenceEquals
est une fonction native/non gérée. L'opérateur =
appelle HashCompare.GenericEqualityIntrinsic<'T>
et aboutit finalement à la fonction interne GenericEqualityObj
. Dans Reflector, cette beauté se décompile en 122 lignes de C #. De toute évidence, l'égalité est une question compliquée. Pour null
-, il suffit de faire une comparaison de références simple pour éviter le coût de la sémantique d'égalité des subtilités.
La correspondance de modèle évite également la surcharge de l'opérateur d'égalité. La fonction suivante fonctionne de manière similaire à 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
Comme indiqué dans le commentaire de Maslow, un opérateur isNull
a été ajouté dans la version 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 déclaré en C # ou 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
telle que publiée par kvb. Par exemple, supposons que l'appelant C # vous fournisse une instance de Random
:
let foo (arg:System.Random) =
if arg <> null then
// do something
Les choses deviennent plus difficiles si l'appelant C # vous donne un type déclaré en F #. Les types déclarés en F # n'ont pas de valeur null
et le compilateur F # ne vous permettra pas de les affecter à null
ni de les comparer à null
. Le problème est que C # ne fait pas cette vérification et un appelant en C # peut toujours vous donner null
. Par exemple:
type MyType(n:int) =
member x.Number = n
Dans ce cas, vous avez besoin de boxe ou de 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 nulles sont généralement découragées en fa #, mais ...
Sans entrer dans les exemples, etc., il existe un article avec quelques exemples @ MSDN ici . Il indique spécifiquement comment détecter un null - et vous pouvez ensuite l'analyser à partir de l'entrée ou le gérer comme vous le souhaitez.
J'ai récemment fait face à un problème similaire . Mon problème était que j'exposais une API développée par F # qui pouvait être utilisée à partir de code C #. C # n'a aucun problème à passer null
à une méthode qui accepte des interfaces ou des 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 nulles.
La raison en est que les types F # n'acceptent pas initialement le littéral null
, alors que techniquement rien dans le CLR ne protège votre API d'être invoquée de manière incorrecte, en particulier à partir d'un langage plus tolérant à la valeur Null tel que C #. Au début, vous ne pourriez pas vous protéger correctement si vous n'utilisez pas l'attribut [<AllowNullLiteral>]
, ce qui est une approche assez laide.
Donc, je suis venu à cette solution dans la rubrique associée , ce qui me permet de garder mon code F # propre et respectueux de F #. En général, je crée une fonction qui accepte n'importe quel objet et le convertit en Option
. Au lieu de null
, je vais valider par rapport à None
. Ceci est similaire à l'utilisation de la fonction prédéfinie dans F # Option.ofObj
, mais cette dernière nécessite que l'objet transmis soit explicitement nullable (donc annoté avec la variable AllowNullLiteral
moche).