Formas únicas de utilizar el operador de fusión nula [cerrado]


164

Sé que la forma estándar de usar el operador de fusión nula en C # es establecer 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 para qué más se ??puede usar? No parece tan útil como el operador ternario, aparte de ser más conciso y más fácil de leer que:

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

Entonces, dado que pocos saben siquiera sobre el operador de fusión nula ...

  • ¿Has usado ??para otra cosa?

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

Respuestas:


216

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"];

12
El encadenamiento es una gran ventaja para el operador, elimina un montón de IF redundantes
chakrit

1
Lo utilicé hoy para reemplazar un bloque IF simple que escribí antes de saber sobre el operador de fusión ternario o nulo. Las ramas verdadera y falsa de la declaración IF original llamaron al mismo método, reemplazando uno de sus argumentos con un valor diferente si una determinada entrada es NULL. Con el operador de fusión nula, es una llamada. ¡Esto es realmente poderoso cuando tienes un método que necesita dos o más sustituciones!
David A. Gray

177

Lo he usado como una línea de carga perezosa:

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

¿Legible? Decide por ti mismo.


66
Hmmm, encontraste un contraejemplo de "por qué alguien querría usarlo como un IF ofuscado" ... eso es realmente muy legible para mí.
Godeke

66
Puede que me falte algo (principalmente uso Java), pero ¿no hay una condición de carrera allí?
Justin K

9
@Justin K: solo hay una condición de carrera si varios subprocesos acceden a la propiedad LazyProp del mismo objeto. Se puede arreglar fácilmente con un bloqueo, si se requiere seguridad de subprocesos de cada instancia. Claramente en este ejemplo, no es obligatorio.
Jeffrey L Whitledge

55
@Jeffrey: Si estuviera claro, no habría hecho la pregunta. :) Cuando vi ese ejemplo, inmediatamente pensé en un miembro singleton, y dado que hago la mayor parte de mi codificación en un entorno multiproceso ... Pero, sí, si asumimos que el código es correcto, cualquier cosa adicional es innecesaria.
Justin K

77
No tiene que ser un Singleton para tener una condición de carrera. Solo una instancia compartida de la clase que contiene LazyProp y varios subprocesos que acceden a LazyProp. Lazy <T> es una mejor manera de hacer este tipo de cosas, y es seguro para subprocesos por defecto (puede optar por modificar la seguridad de subprocesos de Lazy <T>).
Niall Connaughton

52

Lo he encontrado útil en dos formas "ligeramente extrañas":

  • Como alternativa para tener un outparámetro al escribir TryParserutinas (es decir, devolver el valor nulo si falla el análisis)
  • Como una representación de "no sé" para las comparaciones

Este último necesita un poco más de información. Por lo general, cuando crea una comparación con múltiples elementos, debe ver si la primera parte de la comparación (por ejemplo, edad) da una respuesta definitiva, luego la siguiente parte (por ejemplo, nombre) solo si la primera parte no ayudó. El uso del operador de fusión nula significa que puede escribir comparaciones bastante simples (ya sea para ordenar o para igualdad). Por ejemplo, usando un par de clases auxiliares 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 está ordenado.

Se puede hacer lo mismo para verificar la igualdad de referencia (o nulidad) al comienzo de la implementación de Equals.


Me gusta lo que hiciste con PartialComparer, pero estaba buscando casos en los que necesito mantener las variables de expresión evaluadas. No estoy versado en lambdas y extensiones, entonces, ¿podría ver si lo siguiente se adhiere a un patrón similar (es decir, funciona)? stackoverflow.com/questions/1234263/#1241780
maxwellb

33

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 de fusión nula es más limpio y, supongo, más eficiente.


1
+1. Esta es una gran razón por la que me gusta el operador de fusión nula. Es particularmente útil cuando las llamadas MyMethod()tienen algún tipo de efectos secundarios.
un CVn

Si MyMethod()no tiene ningún efecto fuera de devolver un valor, el compilador sabe que no debe llamarlo dos veces, por lo que realmente no tiene que preocuparse por la eficiencia aquí en la mayoría de los casos.
TinyTimZamboni

También mantiene las cosas más legibles en mi humilde opinión cuando se MyMethod()trata de una secuencia encadenada de objetos punteados. Ej:myObject.getThing().getSecondThing().getThirdThing()
xdhmoore

@TinyTimZamboni, ¿tiene una referencia para este comportamiento del compilador?
Kuba Wyrostek

@KubaWyrostek No tengo conocimiento del funcionamiento específico del compilador de C #, pero tengo cierta experiencia con la teoría del compilador estático con llvm. Hay varios enfoques que un compilador puede tomar para optimizar una llamada como esta. Global Value Numbering notará que las dos llamadas a MyMethodson idénticas en este contexto, suponiendo que MyMethodsea ​​una función Pura. Otra opción es la memorización automática o simplemente tener la función cerrada en caché. Por otro lado: en.wikipedia.org/wiki/Global_value_numbering
TinyTimZamboni

23

Otra cosa a considerar es que el operador de fusión no llama al método get de una propiedad dos veces, como lo hace el ternario.

Así que hay escenarios en los que no deberías usar ternary, 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 countvariable será igual a 2, y si usa:

var b = a.Prop ?? 0

la countvariable será igual a 1, como debería ser.


44
Esto merece más votos a favor. He leído tantas veces que ??es equivalente a ?:.
Kuba Wyrostek

1
Punto válido sobre un getter llamado dos veces. Pero en este ejemplo, consideraría que un patrón de diseño incorrecto tiene un getter llamado engañosamente para realizar cambios en el objeto.
Linas

15

La mayor ventaja que encuentro para el ??operador es que puede convertir fácilmente los tipos de valores anulables en tipos no anulables:

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

Frecuentemente uso esto en consultas de Linq:

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

Puede que llegue un poco tarde aquí, pero ese segundo ejemplo arrojará si kvp == null. Y en realidad Nullable<T>tiene un GetValueOrDefaultmétodo que normalmente uso.
CompuChip

66
KeyValuePair es un tipo de valor en .NET Framework, por lo que acceder a cualquiera de sus propiedades nunca arrojaría una excepción de referencia nula. msdn.microsoft.com/en-us/library/5tbh8a42(v=vs.110).aspx
Ryan

9

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 estado de "error", obtengo ese error; de lo contrario, obtengo un valor nulo. Funciona muy bien


Interesante. Estás usando "esto" como una propiedad. Yo nunca he hecho eso.
Armstrongest

Sí, es parte de cómo funciona IDataErrorInfo. En general, esa sintaxis solo es útil en las clases de colección.
Matt Hamilton el

44
Usted está almacenando mensajes de error en this["Name"], this["Address"], etc ??
Andrew

7

Puede usar el operador de fusión nula para que sea un poco más limpio manejar el caso en el que no se establece un parámetro opcional:

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

¿No sería genial si esta línea se pudiera escribir como arg ?= Arg.Default?
Jesse de Wit

6

Me gusta usar el operador de fusión nula para cargar de forma diferida ciertas propiedades.

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

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

Resharper realmente sugerirá esto como un refactor para una carga perezosa "tradicional".
arichards

5

Es ?? necesario, o simplemente debe usar el operador ternario (con el que la mayoría está familiarizado)

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 solo operador ternario en muchos idiomas), por lo que al menos en esa muestra limitada, su declaración falla allí mismo.

Además, como se mencionó anteriormente, hay una situación importante en la que el operador de fusión nula es muy útil, y es cuando la expresión 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 saturar el alcance de la declaración con muchas variables extrañas, incluso si son de corta duración, por lo que (a) también está fuera de lugar Escenario particular.

Por supuesto, si necesita hacer varias verificaciones sobre el resultado, el operador condicional, o un conjunto de ifbloques, es probablemente la herramienta para el trabajo. Pero para el simple "si esto es nulo, úselo, de lo contrario úselo", el operador de fusión nulo ??es perfecto.


Comentario muy tardío de mi parte, pero me alegra ver que alguien cubre que un operador ternario es un operador con tres argumentos (de los cuales ahora hay más de uno en C #).
Steve Kidd

5

Una cosa que he estado haciendo mucho últimamente es usar la fusión nula para hacer 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 una copia de seguridad de largas cadenas ?.que podrían fallar

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

4

El único problema es que el operador de fusión nula 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. Seguro que sería útil tener esto integrado en el Marco.

Pensamientos?


Puede hacerlo con los métodos de extensión, pero estoy de acuerdo, sería una buena adición al código y muy útil en un contexto web.
Armstrongest

Sí, este es un escenario frecuente ... incluso hay un método especial String.IsNullOrEmpty (string) ...
Max Galkin

12
"el operador de fusión nula no detecta cadenas vacías". Bueno, es el operador de fusión nula , no el operador de fusión nullOrEmpty. Y personalmente, desprecio mezclar valores nulos y vacíos en idiomas que distinguen entre los dos, lo que hace que interactuar con cosas que no sean bastante molestas. Y soy un poco obsesivo-compulsivo, por lo que me molesta cuando los lenguajes / implementaciones no distinguen entre los dos de todos modos, incluso si entiendo el razonamiento (como en [la mayoría de las implementaciones de?] SQL).
JAB

3
??no se puede sobrecargar: msdn.microsoft.com/en-us/library/8edha89s(v=vs.100).aspx ; sin embargo, sería genial tenerlo sobrecargado. Yo uso una combinación: s1.Nullify() ?? s2.Nullify()donde string Nullify(this s)devuelve un nullen casos cuando la cadena está vacía.
Kit

¿El único problema? Acabo de encontrarme con ganas de ?? = y encontré este hilo mientras veía si había una manera de hacerlo. (Situación: la primera pasada cargó los casos de excepción, ahora quiero volver y cargar los valores predeterminados en cualquier cosa que ya no se haya cargado).
Loren Pechtel

3

Es ?? necesario, o simplemente debe usar el operador ternario (con el que la mayoría está familiarizado)

Debe usar lo que mejor exprese su intención. Dado que no es un operador se unen nula, usarlo .

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


3

¡Frio! Contarme como alguien que no sabía sobre el operador de fusión nula, es algo 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
}

2

Es un caso de uso un poco extraño, pero tenía un método en el que un IDisposableobjeto se pasa potencialmente como un argumento (y, por lo tanto, está dispuesto por el padre), pero también podría ser nulo (por lo que debería crearse y eliminarse 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 una fusión nula se vuelve mucho más ordenada

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

0

He usado así:

for (int i = 0; i < result.Count; i++)
            {
                object[] atom = result[i];

                atom[3] = atom[3] ?? 0;
                atom[4] = atom[4] != null ? "Test" : string.Empty;
                atom[5] = atom[5] ?? "";
                atom[6] = atom[6] ?? "";
                atom[7] = atom[7] ?? "";
                atom[8] = atom[8] ?? "";
                atom[9] = atom[9] ?? "";
                atom[10] = atom[10] ?? "";
                atom[12] = atom[12] ?? false; 
            }
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.