Operador de fusión de propiedades para C #

9

El operador de fusión nula en C # le permite acortar el código

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

Abajo a:

  return _mywidget ?? new Widget();

Sigo encontrando que un operador útil que me gustaría tener en C # sería uno que le permitiera devolver una propiedad de un objeto, o algún otro valor si el objeto es nulo. Entonces me gustaría reemplazar

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

Con:

  return _mywidget.Length ??! 5;

No puedo evitar pensar que debe haber alguna razón para que este operador no exista. ¿Es un olor a código? ¿Hay alguna forma mejor de escribir esto? (Soy consciente del patrón de objeto nulo, pero parece excesivo usarlo para reemplazar estas cuatro líneas de código).

Ben Fulton
fuente
1
¿Sería suficiente el operador condicional aquí?
Anon
1
Alguien ha escrito algo que le permite hacer algo como esto: string location = employee.office.address.location ?? "Desconocido"; . Esto establecerá la ubicación en "Desconocido" cuando uno de los objetos (ya sea empleado , oficina , dirección o ubicación ) sea nulo. Desafortunadamente, no recuerdo quién lo escribió o dónde lo publicó. Si lo encuentro nuevamente, ¡lo publicaré aquí!
Kristof Claes
1
Esta pregunta obtendría mucha más tracción en StackOverflow.
Trabajo
2
??!es un operador en C ++. :-)
James McNellis
3
Hola 2011, acabo de llamar para decir que C # está obteniendo un operador de navegación seguro
Nathan Cooper

Respuestas:

5

Tengo muchas ganas de una función de lenguaje C # que me permita hacer x.Prop.SomeOtherProp.ThirdProp de forma segura sin tener que verificar que ninguno de ellos sea nulo. En una base de código donde tenía que hacer mucho ese tipo de "recorridos de propiedades profundas", escribí un método de extensión llamado Navigate que tragaba NullReferenceExceptions y en su lugar devolvía un valor predeterminado. Entonces podría hacer algo como esto:

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

La desventaja de esto es que tiene un olor a código diferente: excepciones tragadas. Si desea hacer algo como esto "correcto", puede tomar la lamdba como una expresión y modificar la expresión sobre la marcha para agregar las comprobaciones nulas alrededor de cada acceso de propiedad. Fui por "rápido y sucio" y no lo implementé de esta manera "mejor". Pero tal vez cuando tenga algunos ciclos de pensamiento adicionales, vuelva a visitar esto y lo publique en alguna parte.

Para responder más directamente a su pregunta, supongo que la razón por la que esto no es una función es porque el costo de implementar la función excede el beneficio de tenerla. El equipo de C # tiene que elegir en qué características enfocarse, y esta todavía no ha llegado a la cima. Solo una suposición, aunque no tengo ninguna información privilegiada.

RationalGeek
fuente
1
Exactamente lo que pensaba, pero supongo que el rendimiento de la forma segura sería bastante malo (debido a los muchos Expression.Compile ()) ... Lástima que no esté implementado en C # (algo así como Obj.?PropA.?Prop1)
Guillaume86
x.PropOne.PropTwo.PropThree.PropFour es una mala elección de diseño ya que viola la Ley de Demeter. Rediseñe sus métodos / clases para que no necesite usar esto.
Justin Shield
Evitar inconscientemente el acceso a propiedades profundas a la luz de la Ley de Demeter es tan peligroso como normalizar una base de datos sin pensar. Puedes ir muy lejos.
Mir
3
En C # 6.0 esto es posible utilizando el Operador condicional nulo (también conocido como el operador de Elvis) msdn.microsoft.com/en-us/magazine/dn802602.aspx
victorvartan
15

Creo que ahora podrás hacer esto con C # 6 de manera bastante simple:

return _mywidget?.Length ?? 5;

Tenga en cuenta el operador condicional nulo ?.en el lado izquierdo. _mywidget?.Lengthdevuelve nulo si _mywidgetes nulo.

Sin embargo, un operador ternario simple podría ser más fácil de leer, como otros han sugerido.

Chris Nolet
fuente
9

Realmente es solo una especulación de por qué no lo hicieron. Después de todo, no lo creo. estaba en la primera versión de C #.

De todos modos, simplemente usaría el operador condicional y lo llamaría un día:

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

Los ?? es solo un acceso directo al operador condicional.

whatsisname
fuente
55
Personalmente, esto es tan malo (en mi humilde opinión) como tener un operador para hacer esto en primer lugar. Desea algo de un objeto ... ese objeto podría no estar allí ... así que proporcionemos 5como respuesta en caso de que no esté allí. Debe mirar su diseño antes de comenzar a solicitar operadores especiales.
Moo-Juice
1
@ Moo-Juice Me estoy imaginando a la persona que mantiene este código, '¿Por qué me está dando 5? Hmmmm. ¡¿Qué?!'
msarchet
4

Se parece al operador ternario:

return (_mywidget != NULL) ? _mywidget : new Widget();
Martin York
fuente
3

Personalmente, creo que se debe a la legibilidad. En el primer ejemplo, se ??traduce bastante bien como "¿Estás ahí? No, hagamos esto".

En el segundo ejemplo, ??!es claramente WTF y no significa nada para el programador ... el objeto no existe, por lo que no puedo acceder a la propiedad, así que devolveré 5. ¿Qué significa eso? ¿Qué es 5? ¿Cómo llegamos a la conclusión de que 5 es un buen valor?

En resumen, el primer ejemplo tiene sentido ... no está allí, nuevo . En el segundo, está abierto a debate.

Jugo De Moo
fuente
2
Bueno, tienes que ignorar el hecho de que? no existe Imagina si ?? era común, y si se usaba, tomaba decisiones basadas en eso.
cuál es el
3
Eso me recuerda al llamado "operador WTF" en C ++ (esto realmente funciona:. (foo() != ERROR)??!??! cerr << "Error occurred" << endl;)
Tamás Szelei
@whatsisname, fue más una reflexión sobre el hecho de que era apropiado tomar la decisión. Crear un objeto porque no estaba allí tiene sentido. Asignar un valor arbitrario que no significa nada para el lector porque no se puede obtener una propiedad de algo, es de hecho un escenario WTF.
Moo-Juice
Creo que te estás atrapando en mi ejemplo. Quizás 0 sea mejor que 5. Quizás la propiedad sea un objeto, por lo que si _mywidget es nulo, desea devolver un nuevo foodle (). Estoy completamente en desacuerdo con eso? es más legible que ?? aunque :)
Ben Fulton
@Ben, aunque estoy de acuerdo en que centrarse en el operador en sí mismo no presta demasiado a su pregunta, tracé un paralelismo con la percepción del programador y eso es arbitrario 5. Realmente creo que hay más de un problema que tienes que hacer algo como esto, mientras que en tu primer ejemplo, la necesidad es bastante obvia, especialmente en cosas como los administradores de caché.
Moo-Juice
2

Creo que la gente está demasiado atrapada en el "5" que está regresando ... tal vez 0 hubiera sido mejor :)

De todos modos, creo que el problema es que en ??!realidad no es un operador independiente. Considere qué significaría esto:

var s = myString ??! "";

En ese caso, no tiene sentido: solo tiene sentido si el operando de la izquierda es un descriptor de acceso a la propiedad. O que tal esto:

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

Hay un descriptor de acceso a la propiedad allí, pero todavía no creo que tenga sentido (si myWidgetes así null, ¿eso significa que no llamamos Foo()en absoluto?) ¿O es solo un error?

Creo que el problema es que simplemente no encaja tan naturalmente en el lenguaje como lo ??hace.

Dean Harding
fuente
1

Alguien había creado una clase de utilidad que haría esto por usted. Pero no puedo encontrarlo. Lo que encontré fue algo similar en los foros de MSDN (mira la segunda respuesta).

Con un poco de trabajo, puede extenderlo para evaluar las llamadas a métodos y otras expresiones que la muestra no admite. También puede extenderlo para aceptar un valor predeterminado.

Michael Brown
fuente
1

Entiendo de dónde vienes. Parece como si todos estuvieran envueltos alrededor del eje con el ejemplo que usted proporcionó. Si bien estoy de acuerdo en que los números mágicos son una mala idea, se utilizaría el equivalente de un operador nulo de carbón para ciertas propiedades.

Por ejemplo, tiene objetos vinculados a un mapa y desea que estén a la elevación adecuada. Los mapas DTED pueden tener agujeros en sus datos, por lo que es posible tener un valor nulo, así como valores en el rango de algo como -100 a ~ 8900 metros. Es posible que desee algo en la línea de:

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

El operador de reducción de carbón nula en este caso llenaría la altitud predeterminada si las coordenadas aún no estuvieran configuradas o si el objeto de coordenadas no pudiera cargar datos DTED para esa ubicación.

Veo esto como muy valioso, pero mi especulación sobre por qué no se hizo se limita a la complejidad del compilador. Puede haber algunos casos donde el comportamiento no sería tan predecible.

Berin Loritsch
fuente
1

Cuando no estoy tratando con anidados profundamente, he usado esto (antes del operador de fusión nula introducido en C # 6). Para mí es bastante claro, pero tal vez sea porque estoy acostumbrado, a otras personas les puede resultar confuso.

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

Por supuesto, esto no siempre es aplicable, ya que a veces la propiedad a la que necesita acceder no se puede establecer directamente, o cuando la construcción del objeto en sí es costosa, entre otras razones.

Otra alternativa es usar el patrón NullObject . Defina una única instancia estática de la clase con valores predeterminados razonables, y use eso en su lugar.

return ( _mywidget ?? myWidget.NullInstance).Length;
andyroschy
fuente
0

Los números mágicos suelen tener mal olor. Si el 5 es un número arbitrario, entonces es mejor deletrearlo para que esté documentado más claramente. En mi opinión, una excepción sería si tiene una colección de valores que actúan como valores predeterminados en un contexto particular.

Si tiene un conjunto de valores predeterminados para un objeto, puede subclasificarlo para crear una instancia de singleton predeterminada.

Algo así como: return (myobj ?? default_obj) .Longitud

Chris Quenelle
fuente
0

La propiedad de longitud generalmente es un tipo de valor, por lo que no puede ser nula, por lo que el operador de fusión nula no tiene sentido aquí.

Pero puede hacerlo con una propiedad que es un tipo de referencia, por ejemplo: var window = App.Current.MainWindow ?? new Window();

peancor
fuente
El ejemplo trata de considerar lo que sucedería en su situación si Current fuera nulo. Su ejemplo simplemente se estrellaría ... pero sería útil tener un operador que pueda fusionarse nulamente en cualquier parte de la cadena de dirección incorrecta.
Mir
-1

He visto a alguien con una idea similar antes, pero la mayoría de las veces también querría asignar el nuevo valor al campo nulo, algo así como:

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

así que la idea era combinar la asignación ??y =en:

return _obj ??= new Class();

Creo que esto tiene más sentido que usar un signo de exclamación.

Esta idea no es mía pero me gusta mucho :)

chakrit
fuente
1
¿Eres víctima del pobre ejemplo? Su ejemplo se puede acortar a que return _obj ?? new Class();no hay necesidad del ??=aquí.
Matt Ellen
@ Matt Ellen te equivocaste. La asignación está destinada . Estoy sugiriendo una alternativa diferente. No es exactamente como lo que solicitó el OP, pero creo que será igual de útil.
chakrit
2
¿Por qué quieres la asignación si estás a punto de devolver un valor?
Matt Ellen
inicialización perezosa?
chakrit