Me encontré con esto hoy y no tengo idea de por qué el compilador de C # no arroja un error.
Int32 x = 1;
if (x == null)
{
Console.WriteLine("What the?");
}
Estoy confundido en cuanto a cómo x podría ser nulo. Especialmente porque esta asignación definitivamente arroja un error del compilador:
Int32 x = null;
¿Es posible que x se vuelva nulo? ¿Microsoft simplemente decidió no poner esta comprobación en el compilador o se perdió por completo?
Actualización: después de jugar con el código para escribir este artículo, de repente el compilador apareció con una advertencia de que la expresión nunca sería verdadera. Ahora estoy realmente perdido. Puse el objeto en una clase y ahora la advertencia desapareció, pero quedó con la pregunta, ¿un tipo de valor puede terminar siendo nulo?
public class Test
{
public DateTime ADate = DateTime.Now;
public Test ()
{
Test test = new Test();
if (test.ADate == null)
{
Console.WriteLine("What the?");
}
}
}
if (1 == 2)
. No es trabajo del compilador realizar análisis de ruta de código; para eso están las herramientas de análisis estático y las pruebas unitarias.int
, el compilador genera advertencias agradables. Para los tipos simples, el==
operador está definido por la especificación del lenguaje C #. Para otras estructuras (no de tipo simple), el compilador se olvida de emitir una advertencia. Consulte Advertencia del compilador incorrecto al comparar la estructura con un valor nulo para obtener más detalles. Para estructuras que no son tipos simples, el==
operador debe estar sobrecargado por unopeartor ==
método que sea miembro de la estructura (de lo contrario, no==
se permite).Respuestas:
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 que aceptan valores NULL. El int local se puede convertir en un int que acepta valores NULL. El literal nulo se puede convertir en un int que acepta valores NULL. Por lo tanto, este es un uso legal del operador == y siempre resultará falso.
Del mismo modo, también le permitimos decir "si (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.
fuente
static bool operator == (SomeID a, String b)
y etiquetarloObsolete
? Si el segundo operando es un literal sin tiponull
, sería una mejor coincidencia que cualquier forma que requiera el uso de operadores elevados, pero si es unSomeID?
que resulta igualnull
, el operador elevado ganaría.No es un error, ya que hay una
int?
conversión ( ); genera una advertencia en el ejemplo dado:Si marca el IL, verá que elimina por completo la rama inalcanzable; no existe en una versión de lanzamiento.
Sin embargo, tenga en cuenta que no genera esta advertencia para estructuras personalizadas con operadores de igualdad. Solía hacerlo en 2.0, pero no en el compilador 3.0. El código aún se elimina (por lo que sabe que no se puede acceder al código), 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 elMyValue(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.s v 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); }
fuente
El hecho de que una comparación nunca pueda ser cierta no significa que sea ilegal. No obstante, no, un tipo de valor puede serlo
null
.fuente
null
. Considereint?
, para cuál es azúcar sintácticoNullable<Int32>
, cuál es un tipo de valor.int?
Ciertamente, una variable de tipo podría ser igual anull
.==
operador. Sin embargo, es importante tener en cuenta que la instancia no es realmente nula.No,
Int32 x
nunca se convertiránull
."¿Por qué una comparación de un tipo de valor con nulo es una advertencia?" el artículo te ayudará.
fuente
Un tipo de valor no puede ser
null
, aunque podría ser igual anull
(considerarNullable<>
). En su caso, laint
variable ynull
se conviertenNullable<Int32>
y se comparan implícitamente .fuente
Sospecho que el compilador está optimizando su prueba particular cuando genera el IL, ya que la prueba nunca será falsa.
Nota al margen: ¿Es posible que un Int32 anulable use Int32? x en su lugar.
fuente
Supongo que esto se debe a que "==" es un azúcar de sintaxis que en realidad representa la llamada al
System.Object.Equals
método que acepta elSystem.Object
parámetro. Nulo según la especificación ECMA es un tipo especial que, por supuesto, se deriva deSystem.Object
.Por eso solo hay una advertencia.
fuente
[EDITADO: convirtió las advertencias en errores e hizo que los operadores fueran explícitos sobre los valores nulos en lugar del hack de cadenas].
Según la inteligente sugerencia de @ supercat en un comentario anterior, las siguientes sobrecargas de operadores le permiten generar un error sobre las comparaciones de su tipo de valor personalizado con nulo.
Al implementar operadores que se comparan con versiones anulables de su tipo, el uso de null en una comparación coincide con la versión anulable del operador, lo que le permite generar el error a través del atributo Obsolete.
Hasta que Microsoft nos devuelva la advertencia del compilador, voy a optar por 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 a.x == b.x; } public static bool operator !=(Foo a, Foo b) { return a.x != b.x; } [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; } }
fuente
Foo a; Foo? b; ... if (a == b)...
, aunque tal comparación debería ser perfectamente legítima. La razón por la que sugerí el "truco de cadena" es que permitiría la comparación anterior pero chirríaif (a == null)
. En lugar de usarstring
, se podría sustituir cualquier tipo de referencia que no seaObject
oValueType
; si se desea, se podría definir una clase ficticia con un constructor privado que nunca podría ser llamado y autorizarloReferenceThatCanOnlyBeNull
.Creo que la mejor respuesta a por qué el compilador acepta esto es para clases genéricas. Considere la siguiente clase ...
public class NullTester<T> { public bool IsNull(T value) { return (value == null); } }
Si el compilador no aceptaba comparaciones
null
para tipos de valor, entonces esencialmente rompería esta clase, teniendo una restricción implícita adjunta a su parámetro de tipo (es decir, solo funcionaría con tipos no basados en valores).fuente
El compilador le permitirá comparar cualquier estructura que implemente el
==
a null. Incluso le permite comparar un int con nulo (sin embargo, recibiría una advertencia).Pero si desmonta el código, verá que la comparación se resuelve cuando se compila el código. Entonces, por ejemplo, este código (donde
Foo
se implementa una estructura==
):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 este 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
fuente