Maneras únicas de usar el operador Nulo Coalescente

Sé que la forma estándar de usar el operador de fusión nulo en C # es establecer los valores predeterminados.

string nobody = null; string somebody = "Bob Saget"; string anybody = ""; anybody = nobody ?? "Mr. T"; // returns Mr. T anybody = somebody ?? "Mr. T"; // returns "Bob Saget" 

Pero, ¿qué más puede ?? ¿ser usado para? No parece tan útil como el operador ternario, además de ser más conciso y fácil de leer que:

 nobody = null; anybody = nobody == null ? "Bob Saget" : nobody; // returns Bob Saget 

Entonces, dado que aún menos saben sobre un operador coalescente nulo …

  • ¿Has usado ?? para algo más?

  • Es ?? necesario, o debería usar el operador ternario (con el que la mayoría está familiarizada)

Bueno, antes que nada, es mucho más fácil encadenar que el ternario estándar:

 string anybody = parm1 ?? localDefault ?? globalDefault; 

vs.

 string anyboby = (parm1 != null) ? parm1 : ((localDefault != null) ? localDefault : globalDefault); 

También funciona bien si el objeto nulo-posible no es una variable:

 string anybody = Parameters["Name"] ?? Settings["Name"] ?? GlobalSetting["Name"]; 

vs.

 string anybody = (Parameters["Name"] != null ? Parameters["Name"] : (Settings["Name"] != null) ? Settings["Name"] : GlobalSetting["Name"]; 

Lo he usado como una carga lenta de un solo trazador de líneas:

 public MyClass LazyProp { get { return lazyField ?? (lazyField = new MyClass()); } } 

¿Legible? Decide por ti mismo.

Lo encontré útil en dos formas “un poco extrañas”:

  • Como una alternativa para tener un parámetro out al escribir rutinas TryParse (es decir, devolver el valor nulo si el análisis falla)
  • Como una representación de “no sé” para las comparaciones

Este último necesita un poco más de información. Normalmente, cuando crea una comparación con múltiples elementos, necesita ver si la primera parte de la comparación (por ejemplo, la edad) da una respuesta definitiva, luego la siguiente parte (por ejemplo, nombre) solo si la primera parte no ayudó. Usar el operador nulo coalescente significa que puede escribir comparaciones bastante simples (ya sea para ordenar o para la igualdad). Por ejemplo, usando un par de clases de ayuda en MiscUtil :

 public int Compare(Person p1, Person p2) { return PartialComparer.Compare(p1.Age, p2.Age) ?? PartialComparer.Compare(p1.Name, p2.Name) ?? PartialComparer.Compare(p1.Salary, p2.Salary) ?? 0; } 

Es cierto que ahora tengo ProjectionComparer en MiscUtil, junto con algunas extensiones, que hacen que este tipo de cosas sea aún más fácil, pero aún así está ordenado.

Lo mismo se puede hacer para verificar la igualdad de referencia (o nulidad) al inicio de la implementación de Equals.

Otra ventaja es que el operador ternario requiere una doble evaluación o una variable temporal.

Considere esto, por ejemplo:

 string result = MyMethod() ?? "default value"; 

mientras que con el operador ternario te quedan:

 string result = (MyMethod () != null ? MyMethod () : "default value"); 

que llama a MyMethod dos veces o:

 string methodResult = MyMethod (); string result = (methodResult != null ? methodResult : "default value"); 

De cualquier manera, el operador nulo coalescente es más limpio y, supongo, más eficiente.

Otra cosa a tener en cuenta es que el operador de coalescencia no llama al método get de una propiedad dos veces, como lo hace el ternario.

Entonces hay escenarios donde no deberías usar ternario, por ejemplo:

 public class A { var count = 0; private int? _prop = null; public int? Prop { get { ++count; return _prop } set { _prop = value; } } } 

Si utiliza:

 var a = new A(); var b = a.Prop == null ? 0 : a.Prop; 

el getter se llamará dos veces y la variable de count será igual a 2, y si usa:

 var b = a.Prop ?? 0 

la variable de count será igual a 1, como debería ser.

La mayor ventaja que encuentro para el ?? operador es que puede convertir fácilmente tipos de valores que aceptan valores nulos a tipos que no admiten nulos:

 int? test = null; var result = test ?? 0; // result is int, not int? 

Frecuentemente uso esto en consultas de Linq:

 Dictionary PurchaseQuantities; // PurchaseQuantities populated via ASP .NET MVC form. var totalPurchased = PurchaseQuantities.Sum(kvp => kvp.Value ?? 0); // totalPurchased is int, not int? 

He usado ?? en mi implementación de IDataErrorInfo:

 public string Error { get { return this["Name"] ?? this["Address"] ?? this["Phone"]; } } public string this[string columnName] { get { ... } } 

Si alguna propiedad individual está en un estado de “error”, obtengo ese error; de lo contrario, obtengo un valor nulo. Funciona muy bien

Puede usar el operador nulo coalescente para que sea un poco más limpio para manejar el caso donde no se establece un parámetro opcional:

 public void Method(Arg arg = null) { arg = arg ?? Arg.Default; ... 

Es ?? necesario, o debería usar el operador ternario (con el que la mayoría está familiarizada)

En realidad, mi experiencia es que muy pocas personas están familiarizadas con el operador ternario (o más correctamente, el operador condicional ; ?: Es “ternario” en el mismo sentido que || es binario o + es unario o binario; sin embargo, resulta ser el único operador ternario en muchos idiomas), por lo que al menos en esa muestra limitada, su statement falla allí mismo.

Además, como se mencionó anteriormente, hay una situación importante cuando el operador de fusión nulo es muy útil, y eso es siempre que la expresión que se va a evaluar tiene algún efecto secundario. En ese caso, no puede usar el operador condicional sin (a) introducir una variable temporal, o (b) cambiar la lógica real de la aplicación. (b) claramente no es apropiado en ninguna circunstancia, y si bien es una preferencia personal, no me gusta abarrotar el scope de la statement con muchas variables extrañas, aunque efímeras, por lo que (a) también aparece en ese escenario particular.

Por supuesto, si necesita realizar varias comprobaciones del resultado, el operador condicional o un conjunto de bloques if son probablemente la herramienta para el trabajo. Pero por simple “si esto es nulo, utilícelo, de lo contrario, úselo”, ¿el operador nulo coalescente ?? es perfecto.

El único problema es que el operador nulo-coalescente no detecta cadenas vacías.


es decir

 string result1 = string.empty ?? "dead code!"; string result2 = null ?? "coalesced!"; 

SALIDA:

 result1 = "" result2 = coalesced! 

Actualmente estoy buscando anular el? operador para evitar esto. Seguramente sería útil tener esto incorporado en el Marco.

¿Pensamientos?

Me gusta usar el operador nulo coalesce para cargar ciertas propiedades.

Un ejemplo muy simple (y artificial) solo para ilustrar mi punto:

 public class StackOverflow { private IEnumerable _definitions; public IEnumerable Definitions { get { return _definitions ?? ( _definitions = new List { "definition 1", "definition 2", "definition 3" } ); } } } 

Es ?? necesario, o debería usar el operador ternario (con el que la mayoría está familiarizada)

Debe usar lo que mejor exprese su intención. Como hay un operador de fusión nulo, úselo.

Por otro lado, dado que es tan especializado, no creo que tenga otros usos. Hubiera preferido una sobrecarga apropiada de || operador, como lo hacen otros idiomas. Esto sería más parsimonioso en el diseño del lenguaje. Pero bueno …

¡Guay! Cuentame como alguien que no sabía sobre el operador nulo coalescente, eso es bastante ingenioso.

Me resulta mucho más fácil de leer que el operador ternario.

El primer lugar que me viene a la mente donde podría usarlo es mantener todos mis parámetros predeterminados en un solo lugar.

 public void someMethod( object parm2, ArrayList parm3 ) { someMethod( null, parm2, parm3 ); } public void someMethod( string parm1, ArrayList parm3 ) { someMethod( parm1, null, parm3 ); } public void someMethod( string parm1, object parm2, ) { someMethod( parm1, parm2, null ); } public void someMethod( string parm1 ) { someMethod( parm1, null, null ); } public void someMethod( object parm2 ) { someMethod( null, parm2, null ); } public void someMethod( ArrayList parm3 ) { someMethod( null, null, parm3 ); } public void someMethod( string parm1, object parm2, ArrayList parm3 ) { // Set your default parameters here rather than scattered through the above function overloads parm1 = parm1 ?? "Default User Name"; parm2 = parm2 ?? GetCurrentUserObj(); parm3 = parm3 ?? DefaultCustomerList; // Do the rest of the stuff here } 

Una cosa que he estado haciendo mucho últimamente es usar coalescencia nula para copias de seguridad as . Por ejemplo:

 object boxed = 4; int i = (boxed as int?) ?? 99; Console.WriteLine(i); // Prints 4 

También es útil para hacer copias de seguridad de largas cadenas de ?. que cada uno podría fallar

 int result = MyObj?.Prop?.Foo?.Val ?? 4; string other = (MyObj?.Prop?.Foo?.Name as string)?.ToLower() ?? "not there"; 

Un poco extraño caso de uso, pero tenía un método donde un objeto IDisposable potencialmente se pasa como un arg (y por lo tanto, eliminado por el padre), pero también podría ser nulo (y por lo tanto debe ser creado y eliminado en el método local)

Para usarlo, el código parecía

 Channel channel; Authentication authentication; if (entities == null) { using (entities = Entities.GetEntities()) { channel = entities.GetChannelById(googleShoppingChannelCredential.ChannelId); [...] } } else { channel = entities.GetChannelById(googleShoppingChannelCredential.ChannelId); [...] } 

Pero con un coalesce nulo se vuelve mucho más ordenado

 using (entities ?? Entities.GetEntities()) { channel = entities.GetChannelById(googleShoppingChannelCredential.ChannelId); [...] }