C # está bien al comparar tipos de valores con nulos

Me encontré con esto hoy y no tengo idea de por qué el comstackdor de C # no está arrojando un error.

Int32 x = 1; if (x == null) { Console.WriteLine("What the?"); } 

Estoy confundido sobre cómo x alguna vez podría ser nulo. Especialmente porque esta asignación definitivamente arroja un error de comstackción:

 Int32 x = null; 

¿Es posible que x se vuelva nulo? ¿Microsoft simplemente decidió no colocar esta verificación en el comstackdor o se perdió por completo?

Actualización: después de jugar con el código para escribir este artículo, de repente el comstackdor apareció con una advertencia de que la expresión nunca sería cierta. Ahora estoy realmente perdido. Puse el objeto en una clase y ahora la advertencia se ha ido pero se fue con la pregunta, ¿puede un tipo de valor ser nulo?

 public class Test { public DateTime ADate = DateTime.Now; public Test () { Test test = new Test(); if (test.ADate == null) { Console.WriteLine("What the?"); } } } 

Esto es legal porque la resolución de sobrecarga del operador tiene un mejor operador único para elegir. Hay un operador == que toma dos entradas anulables. El int local es convertible a un int nullable. El literal nulo es convertible a un int nullable. Por lo tanto, este es un uso legal del operador ==, y siempre dará como resultado falso.

Del mismo modo, también te permitimos decir “if (x == 12.6)”, que también siempre será falso. El int local es convertible a doble, el literal es convertible a doble, y obviamente nunca serán iguales.

No es un error, ya que hay una conversión ( int? ); genera una advertencia en el ejemplo dado:

El resultado de la expresión es siempre ‘falso’ ya que un valor de tipo ‘int’ nunca es igual a ‘nulo’ de tipo ‘int’ ‘

Si marca el IL, verá que elimina completamente la twig inalcanzable, no existe en una comstackción de lanzamiento.

Sin embargo, tenga en cuenta que no genera esta advertencia para estructuras personalizadas con operadores de igualdad. Solía ​​en 2.0, pero no en el comstackdor 3.0. El código aún se elimina (por lo que sabe que el código no está disponible), pero no se genera ninguna advertencia:

 using System; struct MyValue { private readonly int value; public MyValue(int value) { this.value = value; } public static bool operator ==(MyValue x, MyValue y) { return x.value == y.value; } public static bool operator !=(MyValue x, MyValue y) { return x.value != y.value; } } class Program { static void Main() { int i = 1; MyValue v = new MyValue(1); if (i == null) { Console.WriteLine("a"); } // warning if (v == null) { Console.WriteLine("a"); } // no warning } } 

Con el IL (para Main ), tenga en cuenta que se ha eliminado todo, excepto MyValue(1) (que podría tener efectos secundarios):

 .method private hidebysig static void Main() cil managed { .entrypoint .maxstack 2 .locals init ( [0] int32 i, [1] valuetype MyValue v) L_0000: ldc.i4.1 L_0001: stloc.0 L_0002: ldloca.sv L_0004: ldc.i4.1 L_0005: call instance void MyValue::.ctor(int32) L_000a: ret } 

esto es básicamente:

 private static void Main() { MyValue v = new MyValue(1); } 

El hecho de que una comparación nunca puede ser cierta no significa que sea ilegal. Sin embargo, no, un tipo de valor puede ser null alguna vez.

No, Int32 x nunca se convertirá en null .

Si está comparando un int con un nulo, entonces el operador de comparación que toma dos int es aplicable.

“¿Por qué una comparación de un tipo de valor con nulo es una advertencia?” el artículo te ayudará.

Un tipo de valor no puede ser null , aunque podría ser igual a null (considere Nullable<> ). En su caso, la variable int y null se Nullable implícitamente en Nullable y se comparan.

Sospecho que el comstackdor optimiza la prueba en particular cuando genera el IL ya que la prueba nunca será falsa.

Nota al margen: ¿Es posible tener un Int32 nulo que use Int32? x en su lugar.

Supongo que esto se debe a que “==” es un azúcar de syntax que en realidad representa una llamada al método System.Object.Equals que acepta el parámetro System.Object . La especificación NULL by ECMA es un tipo especial que, por supuesto, deriva de System.Object .

Es por eso que solo hay una advertencia.

[EDITADO: hizo advertencias sobre errores e hizo explícitos a los operadores sobre anulables en lugar del hack de cadenas.]

Según la sugerencia inteligente de @ supercat en un comentario anterior, las siguientes sobrecargas del operador le permiten generar un error sobre las comparaciones de su tipo de valor personalizado a nulo.

Mediante la implementación de operadores que se comparan con las versiones anulables de su tipo, el uso de null en una comparación coincide con la versión anulable del operador, que le permite generar el error a través del atributo Obsoleto.

Hasta que Microsoft nos devuelva nuestra advertencia del comstackdor, voy con esta solución, ¡gracias @supercat!

 public struct Foo { private readonly int x; public Foo(int x) { this.x = x; } public override string ToString() { return string.Format("Foo {{x={0}}}", x); } public override int GetHashCode() { return x.GetHashCode(); } public override bool Equals(Object obj) { return x.Equals(obj); } public static bool operator ==(Foo a, Foo b) { return ax == bx; } public static bool operator !=(Foo a, Foo b) { return ax != bx; } [Obsolete("The result of the expression is always 'false' since a value of type 'Foo' is never equal to 'null'", true)] public static bool operator ==(Foo a, Foo? b) { return false; } [Obsolete("The result of the expression is always 'true' since a value of type 'Foo' is never equal to 'null'", true)] public static bool operator !=(Foo a, Foo? b) { return true; } [Obsolete("The result of the expression is always 'false' since a value of type 'Foo' is never equal to 'null'", true)] public static bool operator ==(Foo? a, Foo b) { return false; } [Obsolete("The result of the expression is always 'true' since a value of type 'Foo' is never equal to 'null'", true)] public static bool operator !=(Foo? a, Foo b) { return true; } } 

Creo que la mejor respuesta sobre por qué el comstackdor acepta esto es para las clases genéricas. Considere la siguiente clase …

 public class NullTester { public bool IsNull(T value) { return (value == null); } } 

Si el comstackdor no aceptara comparaciones con null para los tipos de valor, esencialmente rompería esta clase, con una restricción implícita asociada a su parámetro de tipo (es decir, solo funcionaría con tipos no basados ​​en el valor).

El comstackdor le permitirá comparar cualquier estructura que implemente el == con nulo. Incluso le permite comparar un int con nulo (sin embargo, recibirá una advertencia).

Pero si desarma el código, verá que la comparación se está resolviendo cuando se comstack el código. Entonces, por ejemplo, este código (donde Foo es una estructura que implementa == ):

 public static void Main() { Console.WriteLine(new Foo() == new Foo()); Console.WriteLine(new Foo() == null); Console.WriteLine(5 == null); Console.WriteLine(new Foo() != null); } 

Genera esta IL:

 .method public hidebysig static void Main() cil managed { .entrypoint // Code size 45 (0x2d) .maxstack 2 .locals init ([0] valuetype test3.Program/Foo V_0) IL_0000: nop IL_0001: ldloca.s V_0 IL_0003: initobj test3.Program/Foo IL_0009: ldloc.0 IL_000a: ldloca.s V_0 IL_000c: initobj test3.Program/Foo IL_0012: ldloc.0 IL_0013: call bool test3.Program/Foo::op_Equality(valuetype test3.Program/Foo, valuetype test3.Program/Foo) IL_0018: call void [mscorlib]System.Console::WriteLine(bool) IL_001d: nop IL_001e: ldc.i4.0 IL_001f: call void [mscorlib]System.Console::WriteLine(bool) IL_0024: nop IL_0025: ldc.i4.1 IL_0026: call void [mscorlib]System.Console::WriteLine(bool) IL_002b: nop IL_002c: ret } // end of method Program::Main 

Como puedes ver:

 Console.WriteLine(new Foo() == new Foo()); 

Se traduce a:

 IL_0013: call bool test3.Program/Foo::op_Equality(valuetype test3.Program/Foo, valuetype test3.Program/Foo) 

Mientras:

 Console.WriteLine(new Foo() == null); 

Se traduce a falso

 IL_001e: ldc.i4.0