¿Cuál es la diferencia entre "x es nulo" y "x == nulo"?

277

En C # 7 podemos usar

if (x is null) return;

en vez de

if (x == null) return;

¿Hay alguna ventaja en usar la nueva forma (ejemplo anterior) sobre la forma antigua?

¿La semántica es diferente?

¿Es solo cuestión de gustos? Si no, ¿cuándo debo usar uno sobre el otro?

Referencia: Novedades en C # 7.0 .

Maniero
fuente
44
ese es el enlace que estaba viendo, sin embargo, no le da mucha información, por eso supongo que el OP está haciendo la pregunta. La parte más importante de la página es que esta prueba es Operador. El operador "es" se usa para verificar si el tipo de tiempo de ejecución de un objeto es compatible con un tipo dado o no. En otras palabras, usamos el operador "es" para verificar que el tipo de objeto es el que esperamos que sea. Veamos su sintaxis:
Simon Price
2
@SimonPrice Se trata de la versión actual de C #: C # 6. Esta pregunta es sobre C # 7, que tiene coincidencia de patrones .
Patrick Hofman el
@bigown ¿qué tipo de detalle estás buscando?
Patrick Hofman el
@PatrickHofman, el tipo de svick respondido, por ejemplo
Maniero el

Respuestas:

233

Actualización: el compilador de Roslyn se ha actualizado para que el comportamiento de los dos operadores sea el mismo cuando no hay un operador de igualdad sobrecargado . Consulte el código en los resultados del compilador actual ( M1y M2en el código) que muestra lo que sucede cuando no hay un comparador de igualdad sobrecargado. Ambos tienen ahora el ==comportamiento de mejor desempeño . Si hay un comparador de igualdad sobrecargado, el código aún difiere .

Consulte las versiones anteriores del compilador de Roslyn en el análisis a continuación.


Porque nullno hay una diferencia con lo que estamos acostumbrados con C # 6. Sin embargo, las cosas se vuelven interesantes cuando cambias nulla otra constante.

Toma esto por ejemplo:

Test(1);

public void Test(object o)
{
    if (o is 1) Console.WriteLine("a");
    else Console.WriteLine("b");
}

La prueba rinde a . Si compara eso con o == (object)1lo que hubiera escrito normalmente, hace una gran diferencia. istoma en consideración el tipo al otro lado de la comparación. ¡Esta genial!

Creo que el patrón == nullvs. vs. is nullconstante es algo que es muy familiar 'por accidente', donde la sintaxis del isoperador y el operador igual producen el mismo resultado.


Como svick comentó, is nullllamadas System.Object::Equals(object, object)donde ==las llamadasceq .

IL para is:

IL_0000: ldarg.1              // Load argument 1 onto the stack
IL_0001: ldnull               // Push a null reference on the stack
IL_0002: call bool [mscorlib]System.Object::Equals(object, object) // Call method indicated on the stack with arguments
IL_0007: ret                  // Return from method, possibly with a value

IL para ==:

IL_0000: ldarg.1              // Load argument 1 onto the stack
IL_0001: ldnull               // Push a null reference on the stack
IL_0002: ceq                  // Push 1 (of type int32) if value1 equals value2, else push 0
IL_0004: ret                  // Return from method, possibly with a value

Como estamos hablando null, no hay diferencia ya que esto solo hace una diferencia en las instancias . Esto podría cambiar cuando haya sobrecargado el operador de igualdad.

Patrick Hofman
fuente
16
@PatrickHofman Parece isllamadas object.Equals(x, null), mientras se ==compila como ceq. Pero el resultado debería ser el mismo, como dijiste.
svick
18
Siempre tenga en cuenta que ==es un operador sobrecargable. Puede tener cualquier comportamiento que desee con él. Por ejemplo, esto implementado de manera extraña== no le dirá si su instancia es realmente nula. is nullpor otro lado, siempre devolverá verdadero para referencias nulas verdaderas :) Además, si tiene ReferenceEqualsen su código, las bombillas VS 2017 sugerirán cambiar a is null, no == null(correctamente).
nawfal
2
@PatrickHofman @svick las dos comprobaciones nulas ahora se compilan a la misma cosa, por lo que isya no tiene la sobrecarga de una llamada a la función cuando se utiliza para verificar nula. Para prueba, vea el enlace publicado por @svick en los comentarios.
AndreasHassing
1
@ AndreasBjørnHassingNielsen Actualizó mi respuesta.
Patrick Hofman
2
@PatrickHofman, ¿no deberían ser las IL al revés? == llama a System.Object :: Equals (objeto, objeto) y es nulo llama a ceq
Zbigniew Ledwoń
68

Operador igual sobrecargado

De hecho, existe una diferencia en la semántica entre las dos comparaciones cuando se compara nullcon un tipo que ha sobrecargado al ==operador. foo is nullutilizará la comparación de referencia directa para determinar el resultado, mientras que foo == null, por supuesto, ejecutará el ==operador sobrecargado si existe.

En este ejemplo, he introducido un "error" en el ==operador sobrecargado , haciendo que siempre arroje una excepción si el segundo argumento es null:

void Main()
{
    Foo foo = null;

    if (foo is null) Console.WriteLine("foo is null"); // This condition is met
    if (foo == null) Console.WriteLine("foo == null"); // This will throw an exception
}

public class Foo
{
    public static bool operator ==(Foo foo1, Foo foo2)
    {
        if (object.Equals(foo2, null)) throw new Exception("oops");
        return object.Equals(foo1, foo2);
    }

    // ...
}

El código IL para foo is null utiliza la ceqinstrucción para realizar una comparación de referencia directa:

IL_0003:  ldloc.0     // foo
IL_0004:  ldnull      
IL_0005:  ceq

El código IL para foo == null utiliza una llamada al operador sobrecargado:

IL_0016:  ldloc.0     // foo
IL_0017:  ldnull      
IL_0018:  call        UserQuery+Foo.op_Equality

Entonces, la diferencia es que si lo usa ==, corre el riesgo de ejecutar código de usuario (que potencialmente puede tener problemas inesperados de comportamiento o rendimiento).

Restricción de genéricos.

El uso de la is nullconstrucción restringe el tipo a un tipo de referencia. El compilador asegura esto, lo que significa que no puede usar is nullun tipo de valor. Si tiene un método genérico, no podrá usarlo a is nullmenos que el tipo genérico esté limitado a ser un tipo de referencia.

bool IsNull<T>(T item) => item is null;                  // Compile error: CS0403
bool IsNull<T>(T item) => item == null;                  // Works
bool IsNull<T>(T item) where T : class => item is null;  // Works

Gracias a David Augusto Villa por señalar esto.

Thorkil Holm-Jacobsen
fuente
2
Además, note (x es nulo) requiere una restricción de clase si x es un tipo genérico, mientras que (x == nulo) y object.ReferenceEquals (x, nulo) no.
David Augusto Villa