Y a-t-il un «opposé» à l'opérateur de fusion nul? (… Dans n'importe quelle langue?)

92

la fusion nulle se traduit à peu près par return x, unless it is null, in which case return y

J'ai souvent besoin return null if x is null, otherwise return x.y

je peux utiliser return x == null ? null : x.y;

Pas mal, mais cela nullme dérange toujours au milieu - cela semble superflu. Je préférerais quelque chose comme return x :: x.y;, où ce qui suit ::est évalué uniquement si ce qui précède ne l'est pas null.

Je vois cela comme presque l'opposé de la coalescence nulle, en quelque sorte mélangée à un contrôle nul en ligne, mais je suis [ presque ] certain qu'il n'y a pas d'opérateur de ce type en C #.

Y a-t-il d'autres langages qui ont un tel opérateur? Si oui, comment s'appelle-t-il?

(Je sais que je peux écrire une méthode pour cela en C #; j'utilise return NullOrValue.of(x, () => x.y);, mais si vous avez quelque chose de mieux, j'aimerais voir cela aussi.)

Geai
la source
Certains ont demandé quelque chose comme x? .Y en C #, mais rien de tel n'existe.
Anthony Pegram
1
@Anthony Oh, ce serait magnifique. Merci.
Jay
2
En C ++, cela serait assez facile à exprimer comme return x ? x.y : NULL. Yay pour convertir les types de pointeurs en booléens!
Phil Miller
1
@Novelocrat, c'est l'une des choses qui m'irrite le plus en C #, c'est qu'ils n'ont pas suivi le C si si (quoi que ce soit) = vrai sauf quand c'est si (0, faux, nul)
Chris Marisic
2
@Chris: ce n'est pas une déclaration précise à propos de C. Si vous avez une variable non scalaire (comme une structure), vous ne pouvez pas l'utiliser dans une condition.
Phil Miller

Réponses:

62

Il y a l' opérateur de déréférencement à sécurité nulle (?.) Dans Groovy ... Je pense que c'est ce que vous recherchez.

(Il est également appelé l' opérateur de navigation sécurisée .)

Par exemple:

homePostcode = person?.homeAddress?.postcode

Cela donnera null si person, person.homeAddressou person.homeAddress.postcodeest nul.

(Ceci est maintenant disponible en C # 6.0 mais pas dans les versions antérieures)

Jon Skeet
la source
1
Groovy a également l '"opérateur Elvis", qui autorise des valeurs par défaut autres que null, par exemple:def bar = foo ?: "<null>"
Craig Stuntz
28
Je nomme cette fonctionnalité pour C # 5.0. Je ne sais pas ou ne me soucie pas de ce qu'est réellement Groovy, mais c'est assez intuitif pour être utilisé dans n'importe quelle langue.
György Andrasek
Il est cependant ajouté à C # 6 maintenant, hourra.
Jeff Mercado
1
Œuvres Safe-navigation pour l'exemple trivial affiché, mais vous avez encore besoin d'utiliser l'opérateur ternaire si vous avez l' intention d'utiliser la valeur non nulle dans un appel de fonction, par exemple: Decimal? post = pre == null ? null : SomeMethod( pre );. Ce serait bien si je pouvais le réduire à "Decimal? Post = pre :: SomeMethod (pre);"
Dai
@Dai: Compte tenu du nombre de permutations que vous pourriez souhaiter, je suis assez content de ce que nous avons.
Jon Skeet
36

Nous avons envisagé d'ajouter?. à C # 4. Il n'a pas fait la coupe; c'est une fonctionnalité "agréable à avoir", pas une fonctionnalité "à avoir". Nous y reviendrons pour de futures versions hypothétiques de la langue, mais je ne retiendrais pas mon souffle si j'étais vous. Il est peu probable que cela devienne plus crucial avec le temps. :-)

Eric Lippert
la source
La facilité d'implémentation d'une fonctionnalité joue-t-elle dans la décision de l'ajouter? Je pose cette question parce que la mise en œuvre de cet opérateur semble être un ajout assez simple et simple au langage. Je ne suis pas un expert (juste un récent diplômé avec un grand amour pour C #), alors corrigez-moi s'il vous plaît!
Nick Strupat
14
@nick: Bien sûr, l'implémentation du lexer et de l'analyseur est de cinq minutes de travail. Ensuite, vous commencez à vous soucier de trucs comme "le moteur IntelliSense le gère-t-il bien?" et "comment le système de récupération d'erreurs le traite-t-il lorsque vous avez tapé le? mais pas le. encore?" et combien de choses différentes fait "." signifie en C # de toute façon, et combien d'entre eux méritent d'avoir un? avant, et quels devraient être les messages d'erreur si vous vous trompez, et combien de tests cette fonctionnalité va-t-elle nécessiter (indice: beaucoup.) Et comment allons-nous la documenter et communiquer le changement, et ...
Eric Lippert
3
@nick: Pour répondre à votre question - oui, le coût de mise en œuvre est un facteur, mais petit. Il n'y a pas de fonctionnalités bon marché au niveau auquel nous travaillons, seulement des fonctionnalités plus ou moins chères. Cinq dollars de travail de développement pour faire fonctionner l'analyseur dans le cas du code correct peuvent facilement se transformer en dizaines de milliers de dollars en conception, mise en œuvre, tests, documentation et éducation.
Eric Lippert
11
@EricLippert Je pense que ce serait l'un des MAJEURS "gentils à avoir", qui ont fait le succès de C # et serviraient parfaitement une mission de rendre le code aussi concis et expressif que possible!
Konstantin
J'aimerais voir cela ajouté, ce que je veux VRAIMENT, c'est l'opérateur elvis: stackoverflow.com/questions/2929836/…
Chris Marisic
16

Si vous avez un type spécial de logique booléenne de court-circuit, vous pouvez le faire (exemple javascript):

return x && x.y;

Si xest nul, alors il ne sera pas évalué x.y.

Eric
la source
2
Sauf que cela court-circuite aussi sur 0, "" et NaN, donc ce n'est pas le contraire de ??. C'est l'opposé de ||.
ritaj
7

Il me semblait juste d'ajouter ceci comme réponse.

Je suppose que la raison pour laquelle il n'y a rien de tel en C # est que, contrairement à l'opérateur de fusion (qui n'est valide que pour les types de référence), l'opération inverse pourrait donner un type de référence ou de valeur (c'est- class xà- dire avec membre int y- donc ce serait malheureusement inutilisable dans de nombreuses situations.

Je ne dis pas, cependant, que je n'aimerais pas le voir!

Une solution potentielle à ce problème serait que l'opérateur lève automatiquement une expression de type valeur sur le côté droit vers une valeur Nullable. Mais alors vous avez le problème que x.yoù y est un entier retournera en fait un int?qui serait pénible.

Une autre solution, probablement meilleure, serait que l'opérateur renvoie la valeur par défaut (c'est-à-dire null ou zéro) pour le type sur le côté droit si l'expression sur la gauche est nulle. Mais ensuite, vous rencontrez des problèmes pour distinguer les scénarios dans lesquels un zéro / null a été lu x.you s'il a été fourni par l'opérateur d'accès sécurisé.

Andras Zoltan
la source
quand j'ai lu la question du PO pour la première fois, ce problème précis m'est venu à l'esprit, mais je ne pouvais pas savoir comment l'articuler. C'est un problème très grave. +1
rmeador
ce n'est pas un problème grave. Utilisez simplement int?comme valeur de retour par défaut, et l'utilisateur peut le changer en int s'il le souhaite. Par exemple, si je souhaite appeler une méthode Lookupavec une valeur par défaut de -1 au cas où l'une de mes références serait null:foo?.Bar?.Lookup(baz) ?? -1
Qwertie
Que diriez-vous d' ?. :être un opérateur ternaire, où le membre droit doit être du même type que le membre approprié donné au milieu?
supercat
6

Delphi a l'opérateur: (plutôt que.), Qui est de sécurité null.

Ils pensaient ajouter un?. opérateur à C # 4.0 pour faire de même, mais cela a obtenu le bloc de hachage.

En attendant, il y a IfNotNull () qui sorte de rayures qui démangent. C'est certainement plus grand que?. ou:, mais cela vous permet de composer une chaîne d'opérations qui ne gênera pas une NullReferenceException si l'un des membres est nul.

48klocs
la source
Pouvez-vous donner un exemple d'utilisation de cet opérateur s'il vous plaît?
Max Carroll
1
Le ?.est une fonctionnalité du langage C # 6.0, et oui, il fait exactement ce que l'OP a demandé ici. Mieux vaut tard que jamais ^^
T_D
3

Dans Haskell, vous pouvez utiliser l' >>opérateur:

  • Nothing >> Nothing est Nothing
  • Nothing >> Just 1 est Nothing
  • Just 2 >> Nothing est Nothing
  • Just 2 >> Just 1 est Just 1
dave4420
la source
3

Haskell a fmap, ce qui dans ce cas, je pense, équivaut à Data.Maybe.map. Haskell est purement fonctionnel, donc ce que vous recherchez serait

fmap select_y x

Si xc'est le cas Nothing, cela revient Nothing. Si xc'est le cas Just object, cela revient Just (select_y object). Pas aussi joli que la notation par points, mais étant donné qu'il s'agit d'un langage fonctionnel, les styles sont différents.

Norman Ramsey
la source
2

PowerShell vous permet de référencer des propriétés (mais pas d'appeler des méthodes) sur une référence null et il renverra null si l'instance est null. Vous pouvez le faire à n'importe quelle profondeur. J'avais espéré que la fonctionnalité dynamique de C # 4 prendrait en charge cela, mais ce n'est pas le cas.

$x = $null
$result = $x.y  # $result is null

$x = New-Object PSObject
$x | Add-Member NoteProperty y 'test'
$result = $x.y  # $result is 'test'

Ce n'est pas joli mais vous pouvez ajouter une méthode d'extension qui fonctionnera comme vous le décrivez.

public static TResult SafeGet<T, TResult>(this T obj, Func<T, TResult> selector) {
    if (obj == null) { return default(TResult); }
    else { return selector(obj); }
}

var myClass = new MyClass();
var result = myClass.SafeGet(x=>x.SomeProp);
Josh
la source
2
public class ok<T> {
    T s;
    public static implicit operator ok<T>(T s) { return new ok<T> { s = s }; }
    public static implicit operator T(ok<T> _) { return _.s; }

    public static bool operator true(ok<T> _) { return _.s != null; }
    public static bool operator false(ok<T> _) { return _.s == null; }
    public static ok<T> operator &(ok<T> x, ok<T> y) { return y; }
}

J'ai souvent besoin de cette logique pour les chaînes:

using ok = ok<string>;

...

string bob = null;
string joe = "joe";

string name = (ok)bob && bob.ToUpper();   // name == null, no error thrown
string boss = (ok)joe && joe.ToUpper();   // boss == "JOE"
Prix ​​de J Bryan
la source
1

Créez une instance statique de votre classe quelque part avec toutes les bonnes valeurs par défaut pour les membres.

Par exemple:

z = new Thingy { y=null };

alors au lieu de ton

return x != null ? x.y : null;

tu peux écrire

return (x ?? z).y;
pseudo
la source
J'utilise parfois cette technique (par exemple (x ?? "") .ToString ()) mais elle n'est pratique que sur quelques types de données tels que les POD et les chaînes.
Qwertie
1

Le soi-disant «opérateur conditionnel nul» a été introduit dans C # 6.0 et Visual Basic 14.
Dans de nombreuses situations, il peut être utilisé comme l'opposé exact de l'opérateur de fusion null:

int? length = customers?.Length; // null if customers is null   
Customer first = customers?[0];  // null if customers is null  
int? count = customers?[0]?.Orders?.Count();  // null if customers, the first customer, or Orders is null

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/null-conditional-operators

Jpsy
la source