Opérateur de fusion de propriétés pour C #


9

L'opérateur de coalescence nulle en c # vous permet de raccourcir le code

  if (_mywidget == null)
     return new Widget();
  else
     return _mywidget;

Jusqu'à:

  return _mywidget ?? new Widget();

Je continue de trouver qu'un opérateur utile que j'aimerais avoir en C # serait celui qui vous permettrait de renvoyer une propriété d'un objet, ou une autre valeur si l'objet est nul. Je voudrais donc remplacer

  if (_mywidget == null)
     return 5;
  else
     return _mywidget.Length;

Avec:

  return _mywidget.Length ??! 5;

Je ne peux m'empêcher de penser qu'il doit y avoir une raison pour que cet opérateur n'existe pas. Est-ce une odeur de code? Y a-t-il une meilleure façon d'écrire cela? (Je connais le modèle d'objet nul mais il semble exagéré de l'utiliser pour remplacer ces quatre lignes de code.)


1
L'opérateur conditionnel suffirait-il ici?
Anon.

1
Quelqu'un a écrit quelque chose qui vous permet de faire quelque chose comme ceci: string location = employee.office.address.location ?? "Inconnue"; . Ce paramètre définira l' emplacement sur "Inconnu" lorsque l'un des objets ( employé , bureau , adresse ou emplacement ) est nul. Malheureusement, je ne me souviens pas qui l'a écrit ni où il l'a posté. Si je le retrouve, je le posterai ici!
Kristof Claes

1
Cette question obtiendrait beaucoup plus de traction sur StackOverflow.
Job

2
??!est un opérateur en C ++. :-)
James McNellis

3
Bonjour 2011, vient d'appeler pour dire que c # obtient un opérateur de navigation en toute sécurité
Nathan Cooper

Réponses:


5

Je veux vraiment une fonctionnalité de langage C # qui me permette de faire en toute sécurité x.Prop.SomeOtherProp.ThirdProp sans avoir à vérifier chacun de ceux-ci pour null. Dans une base de code où j'ai dû faire beaucoup ce genre de "traversées de propriétés profondes", j'ai écrit une méthode d'extension appelée Navigate qui a avalé NullReferenceExceptions et a renvoyé à la place une valeur par défaut. Je pourrais donc faire quelque chose comme ça:

var propVal = myThing.Navigate(x => x.PropOne.PropTwo.PropThree.PropFour, defaultValue);

L'inconvénient est qu'il a une odeur de code différente: les exceptions avalées. Si vous vouliez faire quelque chose comme ceci "à droite", vous pourriez prendre la lamdba comme expression et modifier l'expression à la volée pour ajouter les vérifications nulles autour de chaque accesseur de propriété. Je suis allé pour "rapide et sale" et ne l'ai pas implémenté de cette façon "meilleure". Mais peut-être que lorsque j'aurai quelques cycles de réflexion de rechange, je reverrai cela et le posterai quelque part.

Pour répondre plus directement à votre question, je suppose que la raison pour laquelle il ne s'agit pas d'une fonctionnalité est que le coût de mise en œuvre de la fonctionnalité dépasse les avantages de la fonctionnalité. L'équipe C # doit choisir les fonctionnalités sur lesquelles se concentrer, et celle-ci n'a pas encore atteint le sommet. Juste une supposition même si je n'ai pas d'informations privilégiées.


1
Exactement ce que je pensais, mais les performances de la manière sûre seraient assez mauvaises je suppose (à cause des nombreuses Expression.Compile ()) ... Dommage qu'il ne soit pas implémenté en C # (quelque chose comme Obj.?PropA.?Prop1)
Guillaume86

x.PropOne.PropTwo.PropThree.PropFour est un mauvais choix de conception car il viole la loi de Déméter. Reconcevoir vos méthodes / classes afin que vous n'ayez pas besoin de l'utiliser.
Justin Shield

Éviter sans réfléchir l'accès profond à la propriété à la lumière de la loi de Déméter est aussi dangereux que de normaliser sans réfléchir une base de données. Tu peux aller trop loin.
Mir

3
En C # 6.0, cela est possible en utilisant l'opérateur Null-Conditionnel (alias l'opérateur Elvis) msdn.microsoft.com/en-us/magazine/dn802602.aspx
victorvartan

15

Je pense que vous pourrez le faire avec C # 6 tout simplement maintenant:

return _mywidget?.Length ?? 5;

Notez l'opérateur à condition nulle ?.sur le côté gauche. _mywidget?.Lengthrenvoie null si _mywidgetest nul.

Un simple opérateur ternaire pourrait être plus facile à lire, comme d'autres l'ont suggéré.


9

Ce n'est vraiment qu'une spéculation sur la raison pour laquelle ils ne l'ont pas fait. Après tout, je ne crois pas ?? était dans la première version C #.

Quoi qu'il en soit, je voudrais simplement utiliser l'opérateur conditionnel et l'appeler un jour:

return (_mywidget != null) ? _mywidget.Length : 5;

Le ?? est juste un raccourci vers l'opérateur conditionnel.


5
Personnellement, c'est tout aussi mauvais (à mon humble avis) que d'avoir un opérateur pour le faire en premier lieu. Vous voulez quelque chose d'un objet ... cet objet pourrait ne pas être là ... alors fournissons 5comme réponse au cas où il ne serait pas là. Vous devez examiner votre conception avant de commencer à demander des opérateurs spéciaux.
Moo-Juice

1
@ Moo-Juice J'imagine juste la personne qui maintient ce code, 'Pourquoi est-ce que ça me donne 5? Hrmmm. Quoi?!'
msarchet

4

Il ressemble à l'opérateur ternaire:

return (_mywidget != NULL) ? _mywidget : new Widget();

3

Personnellement, je pense que c'est dû à la lisibilité. Dans le premier exemple, le se ??traduit assez bien par "Êtes-vous là ?? Non, faisons cela".

Dans le deuxième exemple, ??!est clairement WTF et ne signifie rien pour le programmeur .... l'objet n'existe pas, donc je ne peux pas accéder à la propriété donc je vais en retourner 5. Qu'est-ce que cela signifie même? Qu'est-ce que 5? Comment arrivons-nous à la conclusion que 5 est une bonne valeur?

Bref, le premier exemple a du sens ... pas là, c'est nouveau . Dans le second, c'est ouvert au débat.


2
Eh bien, vous devez ignorer le fait que ??! n'existe pas. Imaginez si ??! était courant, et s'il était utilisé, alors prendre des décisions en fonction de cela.
whatsisname

3
Cela me rappelle le soi-disant "opérateur WTF" en C ++ (cela fonctionne en fait (foo() != ERROR)??!??! cerr << "Error occurred" << endl;
:.

@whatsisname, c'était plutôt une réflexion sur le fait que cela était approprié pour la décision prise. Créer un objet parce qu'il n'était pas là a du sens. Attribuer une valeur arbitraire qui ne signifie rien pour le lecteur parce que vous ne pouvez pas accéder à une propriété de quelque chose, est en effet un scénario WTF.
Moo-Juice

Je pense que vous vous retrouvez dans mon exemple. Peut-être que 0 vaut mieux que 5. Peut-être que la propriété est un objet, donc si _mywidget est nul, vous voulez retourner un nouveau foodle (). Je suis complètement en désaccord avec ça ?? est plus lisible que ??! cependant :)
Ben Fulton

@Ben, Bien que je convienne que se concentrer sur l'opérateur lui-même ne prête pas trop à votre question, je faisais un parallèle avec la perception du programmeur et celle arbitraire 5. Je pense vraiment qu'il y a plus de problème que vous devez faire quelque chose comme ça, alors que dans votre premier exemple, le besoin est assez évident, en particulier dans des choses telles que les gestionnaires de cache.
Moo-Juice

2

Je pense que les gens sont trop pris dans le "5" que vous revenez ... peut-être que 0 aurait été mieux :)

Quoi qu'il en soit, je pense que le problème est que ce ??!n'est pas réellement un opérateur autonome. Considérez ce que cela signifierait:

var s = myString ??! "";

Dans ce cas, cela n'a aucun sens: cela n'a de sens que si l'opérande de gauche est un accesseur de propriété. Ou qu'en est-il:

var s = Foo(myWidget.Length) ??! 0;

Il y a un accesseur de propriété là-dedans, mais je ne pense toujours pas que cela ait du sens (si myWidgetoui null, cela signifie-t-il que nous n'appelons pas Foo()du tout?), Ou est-ce juste une erreur?

Je pense que le problème est qu'il ne rentre tout simplement pas aussi naturellement dans la langue ??.


1

Quelqu'un avait créé une classe utilitaire qui ferait cela pour vous. Mais je ne le trouve pas. Ce que j'ai trouvé était quelque chose de similaire sur les forums MSDN (regardez la deuxième réponse).

Avec un peu de travail, vous pouvez l'étendre pour évaluer les appels de méthode et d'autres expressions que l'exemple ne prend pas en charge. Vous pouvez également l'étendre pour accepter une valeur par défaut.


1

Je comprends d'où tu viens. Il semble que tout le monde s'enroule autour de l'essieu avec l'exemple que vous avez fourni. Bien que je convienne que les nombres magiques sont une mauvaise idée, il y aurait lieu d'utiliser l'équivalent d'un opérateur de coalescence nul pour certaines propriétés.

Par exemple, vous avez des objets liés à une carte et vous souhaitez qu'ils soient à la bonne altitude. Les cartes DTED peuvent avoir des trous dans leurs données, il est donc possible d'avoir une valeur nulle, ainsi que des valeurs de l'ordre de -100 à ~ 8900 mètres. Vous voudrez peut-être quelque chose dans le sens de:

mapObject.Altitude = mapObject.Coordinates.DtedAltitude ?? DEFAULT_ALTITUDE;

L'opérateur null coallescing dans ce cas remplirait l'altitude par défaut si les coordonnées n'étaient pas encore définies, ou si l'objet coordonnées ne pouvait pas charger les données DTED pour cet emplacement.

Je vois cela comme très précieux, mais mes spéculations sur la raison pour laquelle cela n'a pas été fait se limitent à la complexité du compilateur. Il peut y avoir des cas où le comportement ne serait pas aussi prévisible.


1

Lorsque je ne traite pas avec des imbrications profondes, j'ai utilisé cela (avant l'opérateur de coalescence nulle introduit en C # 6). Pour moi, c'est assez clair, mais c'est peut-être parce que je suis habitué, d'autres personnes pourraient trouver cela déroutant.

return ( _mywidget ?? new MyWidget() {length = defaultLength}).Length;

Bien sûr, cela n'est pas toujours applicable, car parfois la propriété à laquelle vous devez accéder ne peut pas être définie directement, ou lorsque la construction de l'objet lui-même est coûteuse, entre autres raisons.

Une autre alternative consiste à utiliser le modèle NullObject . Vous définissez une seule instance statique de la classe avec des valeurs par défaut raisonnables et utilisez-la à la place.

return ( _mywidget ?? myWidget.NullInstance).Length;

0

Les nombres magiques ont généralement une mauvaise odeur. Si le 5 est un nombre arbitraire, il est préférable de le préciser afin qu'il soit documenté plus clairement. Une exception à mon avis serait si vous avez une collection de valeurs qui agissent comme des valeurs par défaut dans un contexte particulier.

Si vous avez un ensemble de valeurs par défaut pour un objet, vous pouvez le sous-classer pour créer une instance singleton par défaut.

Quelque chose comme: return (myobj ?? default_obj) .Length


0

La propriété de longueur est généralement un type de valeur, elle ne peut donc pas être nulle, donc l'opérateur de coalescence nulle n'a pas de sens ici.

Mais vous pouvez le faire avec une propriété qui est un type de référence, par exemple: var window = App.Current.MainWindow ?? new Window();


L'exemple essaie de considérer ce qui se passerait dans votre situation si Current était nul. Votre exemple se bloquerait simplement ... mais avoir un opérateur qui peut annuler la fusion n'importe où dans la chaîne de mauvaise direction serait pratique.
Mir

-1

J'ai déjà vu quelqu'un avec une idée similaire, mais la plupart du temps, vous souhaitez également affecter la nouvelle valeur dans le champ nul, quelque chose comme:

return _obj ?? (_obj = new Class());

donc l'idée était de combiner le ??et l' =affectation en:

return _obj ??= new Class();

Je pense que cela a plus de sens que d'utiliser un point d'exclamation.

Cette idée n'est pas la mienne mais je l'aime vraiment :)


1
Êtes-vous victime du pauvre exemple? Votre exemple peut être raccourci pour return _obj ?? new Class();qu'il n'y ait pas besoin de l' ??=ici.
Matt Ellen

@Matt Ellen, vous vous êtes trompé. La mission est prévue . Je propose une alternative différente. Ce n'est pas exactement ce que le PO a demandé, mais je pense que ce sera tout aussi utile.
chakrit

2
pourquoi voulez-vous l'affectation si vous êtes sur le point de retourner une valeur?
Matt Ellen

initialisation paresseuse?
chakrit
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.