¿Existe un "opuesto" al operador coalescente nulo? (…¿en cualquier idioma?)

92

la coalescencia nula se traduce aproximadamente en return x, unless it is null, in which case return y

A menudo necesito return null if x is null, otherwise return x.y

Puedo usar return x == null ? null : x.y;

No está mal, pero eso nullen el medio siempre me molesta, parece superfluo. Preferiría algo como return x :: x.y;, donde lo que sigue a ::se evalúa solo si lo que le precede no lo es null.

Veo esto como casi un opuesto a la coalescencia nula, algo mezclado con una verificación nula en línea concisa, pero estoy [ casi ] seguro de que no existe tal operador en C #.

¿Hay otros idiomas que tengan un operador de este tipo? Si es así, ¿cómo se llama?

(Sé que puedo escribir un método para ello en C #; lo uso return NullOrValue.of(x, () => x.y);, pero si tienes algo mejor, me gustaría verlo también).

Arrendajo
fuente
Algunos han pedido algo como x? .Y en C #, pero no existe nada de eso.
Anthony Pegram
1
@Anthony Oh, eso sería hermoso. Gracias.
Jay
2
En c ++, eso sería bastante fácil de expresar como return x ? x.y : NULL. ¡Bien por convertir tipos de puntero a booleanos!
Phil Miller
1
@Novelocrat esa es una de las cosas que más me irrita en C # es que no siguieron la C si eso si (cualquier cosa) = verdadero excepto cuando es si (0, falso, nulo)
Chris Marisic
2
@Chris: esa no es una declaración precisa sobre C. Si tiene una variable no escalar (como una estructura), no puede usarla en una condición.
Phil Miller

Respuestas:

62

Ahí está el operador de desreferencia nula de fallos (?.) En el maravilloso ... Creo que eso es lo que está buscando.

(También se llama operador de navegación segura ).

Por ejemplo:

homePostcode = person?.homeAddress?.postcode

Esto dará nulo si person, person.homeAddresso person.homeAddress.postcodees nulo.

(Esto ahora está disponible en C # 6.0 pero no en versiones anteriores)

Jon Skeet
fuente
1
Groovy también tiene el "operador de Elvis", que permite valores predeterminados distintos a null, por ejemplo:def bar = foo ?: "<null>"
Craig Stuntz
28
Nomino esta característica para C # 5.0. No sé ni me importa qué es realmente Groovy, pero es lo suficientemente intuitivo como para usarlo en cualquier idioma.
György Andrasek
Sin embargo, ahora se está agregando a C # 6, ¡hurra!
Jeff Mercado
1
Obras segura-navegación para el ejemplo trivial publicado, pero todavía se tienen que utilizar el operador ternario si tiene intención de utilizar el valor no nulo en una llamada a la función, por ejemplo: Decimal? post = pre == null ? null : SomeMethod( pre );. Sería bueno si pudiera reducirlo a "Decimal? Post = pre :: SomeMethod (pre);"
Dai
@Dai: Dada la cantidad de permutaciones que podría desear de eso, estoy bastante contento con lo que tenemos.
Jon Skeet
36

Consideramos agregar ?. a C # 4. No hizo el corte; es una característica "agradable de tener", no una característica "imprescindible". Lo consideraremos de nuevo para futuras versiones hipotéticas del idioma, pero no aguantaría la respiración esperando si fuera usted. No es probable que se vuelva más crucial a medida que pasa el tiempo. :-)

Eric Lippert
fuente
¿La facilidad de implementar una función influye en la decisión de agregarla? Pregunto esto porque implementar ese operador parece una adición bastante directa y simple al lenguaje. No soy un experto (solo un recién graduado con un gran amor por C #), ¡así que corrígeme!
Nick Strupat
14
@nick: Claro, implementar el lexer y parser es un trabajo de cinco minutos. Entonces empiezas a preocuparte por cosas como "¿el motor IntelliSense lo maneja bien?" y "¿cómo lo trata el sistema de recuperación de errores cuando ha escrito el? pero no el. ¿todavía?" y cuántas cosas diferentes hace "." significa en C # de todos modos, y ¿cuántos de ellos merecen tener un? antes, y cuáles deberían ser los mensajes de error si se equivoca, y cuántas pruebas va a necesitar esta función (pista: muchas). Y cómo vamos a documentarlo y comunicar el cambio, y ...
Eric Lippert
3
@nick: Para responder a su pregunta, sí, el costo de implementación es un factor, pero pequeño. No hay funciones baratas en el nivel en el que trabajamos, solo funciones más o menos costosas. Cinco dólares de trabajo de desarrollo para hacer que el analizador funcione en el caso del código correcto pueden convertirse fácilmente en decenas de miles de dólares en diseño, implementación, pruebas, documentación y educación.
Eric Lippert
11
@EricLippert Creo que este sería uno de los MÁS "agradables", que han hecho que C # sea tan exitoso y que también cumpliría perfectamente la misión de hacer que el código sea lo más conciso y expresivo posible.
Konstantin
Me gustaría ver esto agregado, lo que REALMENTE quiero es el operador de elvis: stackoverflow.com/questions/2929836/…
Chris Marisic
16

Si tiene un tipo especial de lógica booleana de cortocircuito, puede hacer esto (ejemplo de javascript):

return x && x.y;

Si xes nulo, no se evaluará x.y.

Eric
fuente
2
Excepto que esto también provoca un cortocircuito en 0, "" y NaN, por lo que no es lo contrario de ??. Es lo opuesto a ||.
ritaj
7

Simplemente me pareció correcto agregar esto como respuesta.

Supongo que la razón por la que no existe tal cosa en C # es porque, a diferencia del operador coalescente (que solo es válido para tipos de referencia), la operación inversa podría producir una referencia o un tipo de valor (es decir, class xcon miembro int y, por lo que desafortunadamente sería inutilizable en muchas situaciones.

Sin embargo, no estoy diciendo que no me gustaría verlo.

Una posible solución a ese problema sería que el operador elevara automáticamente una expresión de tipo de valor en el lado derecho a un valor anulable. Pero luego tiene el problema de que x.ydonde y es un int, en realidad devolverá un, lo int?que sería una molestia.

Otra solución, probablemente mejor, sería que el operador devolviera el valor predeterminado (es decir, nulo o cero) para el tipo del lado derecho si la expresión del lado izquierdo es nula. Pero luego tiene problemas para distinguir escenarios donde realmente se leyó un cero / nulo x.yo si fue proporcionado por el operador de acceso seguro.

Andras Zoltan
fuente
cuando leí por primera vez la pregunta del OP, este problema exacto apareció en mi cabeza, pero no pude precisar cómo articularlo. Es un problema muy serio. +1
rmeador
no es un problema grave. Solo utilícelo int?como valor de retorno predeterminado, y el usuario puede cambiarlo a int si lo desea. Por ejemplo, si me gustaría llamar a algún método Lookupcon un valor predeterminado de -1 en caso de que una de mis referencias sea null:foo?.Bar?.Lookup(baz) ?? -1
Qwertie
¿Qué tal ?. :ser un operador ternario, donde se requiere que el lado derecho sea del mismo tipo que el miembro apropiado dado en el medio?
supercat
6

Delphi tiene el operador: (en lugar de.), Que es seguro para nulos.

Estaban pensando en agregar un?. operador a C # 4.0 para hacer lo mismo, pero eso consiguió el bloque de corte.

Mientras tanto, hay IfNotNull () que tipo de rasguños pican. Ciertamente es más grande que ?. o:, pero le permite componer una cadena de operaciones que no le enviará una NullReferenceException si uno de los miembros es nulo.

48klocs
fuente
¿Puede dar un ejemplo del uso de este operador, por favor?
Max Carroll
1
El ?.es una característica del lenguaje C # 6.0, y sí que hace exactamente lo que el PO pidió aquí. Más vale tarde que nunca ^^
T_D
3

En Haskell, puede utilizar el >>operador:

  • Nothing >> Nothing es Nothing
  • Nothing >> Just 1 es Nothing
  • Just 2 >> Nothing es Nothing
  • Just 2 >> Just 1 es Just 1
dave4420
fuente
3

Haskell tiene fmap, que en este caso creo que es equivalente a Data.Maybe.map. Haskell es puramente funcional, así que lo que estás buscando sería

fmap select_y x

Si xes así Nothing, esto vuelve Nothing. Si xes así Just object, esto vuelve Just (select_y object). No es tan bonito como la notación de puntos, pero dado que es un lenguaje funcional, los estilos son diferentes.

Norman Ramsey
fuente
2

PowerShell le permite hacer referencia a propiedades (pero no a métodos de llamada) en una referencia nula y devolverá nulo si la instancia es nula. Puede hacer esto a cualquier profundidad. Tenía la esperanza de que la función dinámica de C # 4 fuera compatible con esto, pero no es así.

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

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

No es bonito, pero puede agregar un método de extensión que funcione como lo describe.

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
fuente
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; }
}

A menudo necesito esta lógica para cadenas:

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"
Precio de J Bryan
fuente
1

Cree una instancia estática de su clase en algún lugar con todos los valores predeterminados correctos para los miembros.

Por ejemplo:

z = new Thingy { y=null };

entonces en lugar de tu

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

puedes escribir

return (x ?? z).y;
Mella
fuente
A veces utilizo esa técnica (por ejemplo, (x ?? "") .ToString ()) pero solo es práctica en unos pocos tipos de datos, como POD y cadenas.
Qwertie
1

El llamado "operador condicional nulo" se introdujo en C # 6.0 y en Visual Basic 14.
En muchas situaciones se puede utilizar como exactamente lo contrario del operador coalescente nulo:

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
fuente