safety operator operador c# compiler-construction null this

c# - operator - ¿Por qué el compilador no advierte al menos en este== nulo?



null safety c# (5)

Wow ... Supongo que estaba vergonzosamente mal

Estoy en desacuerdo. Creo que todavía haces un buen punto.

El compilador sabe si la comparación irá a un operador de comparación definido por el usuario o no, y el compilador sabe que si no lo hace, entonces ''esto'' nunca es nulo.

Y, de hecho, el compilador realiza un seguimiento de si una expresión dada puede ser legalmente nula o no, para implementar una optimización menor en llamadas a métodos no virtuales. Si tienes un método no virtual M y dices foo.M(); entonces el compilador genera esto como "hacer una llamada virtual a M con el receptor foo". ¿Por qué? Porque queremos lanzar si foo es nulo, y una llamada virtual siempre hace una comprobación nula en el receptor. Una llamada no virtual no lo hace; tendríamos que generarlo como "compruebe foo para nulo, y luego hagamos una llamada no virtual a M", que es un código más largo, más lento e irritante.

Ahora, si podemos escapar sin hacer la comprobación nula, lo hacemos. Si dices this.M() (new Foo()).M() this.M() o (new Foo()).M() entonces NO generamos una llamada virtual. Generamos la llamada no virtual sin la comprobación nula porque sabemos que no puede ser nula.

Por lo tanto, el compilador tiene excelentes datos sobre si una comparación particular con null a veces, siempre o nunca tendrá éxito.

La pregunta entonces es "si el compilador sabe que una comparación en particular nunca tendrá éxito, ¿por qué no generar una advertencia para ella?"

Y la respuesta es "a veces lo hacemos, y otras veces no".

Lo hacemos en esta situación:

int x = 123; if (x == null) ...

Hay un operador de igualdad definido en dos puntos nulables. x es convertible a int. null es convertible a int. Así que ese operador de igualdad es válido, y por lo tanto se usa, y por supuesto siempre es falso. El compilador advierte que la expresión siempre es falsa.

Sin embargo, debido a un error que introdujimos accidentalmente en C # 3, este código NO produce esa advertencia:

Guid x = whatever; if (x == null) ...

El mismo trato. El operador de igualdad guid que acepta nulos es válido, pero se suprime la advertencia. No estoy seguro de si arreglamos este error para C # 4 o no. Si no, espero que lo pongamos en un paquete de servicio.

En cuanto a "if (this == null)", no sé por qué no lo advertimos. Ciertamente parece un buen candidato para una advertencia. La explicación más probable es seguir este silogismo lógico:

  • Las advertencias son características del compilador.
  • las características del compilador deben ser (1) pensadas, (2) diseñadas, (3) implementadas, (4) probadas, (5) documentadas y (6) enviadas a usted antes de poder aprovechar las ventajas de la característica.
  • Nadie ha hecho ninguna de esas seis cosas necesarias para esta característica; No podemos enviar características que nunca pensamos en primer lugar.
  • por lo tanto, no hay tal característica.

Hay infinitas características del compilador que aún no hemos pensado; No hemos implementado ninguno de ellos.

Otra razón para no dar una advertencia aquí es que intentamos dar advertencias en el código que es probable que se escriba por accidente y que sea casi seguro que sea incorrecto por una razón no obvia . "this == null" no es probable que se escriba por accidente, y aunque es casi seguro que esté incorrecto, como se observa en la declaración de su pregunta, obviamente está equivocado.

Compare eso con nuestro "guid == null": es probable que ocurra por accidente, ya que un desarrollador podría pensar accidentalmente que Guid es un tipo de referencia. Las guías se suelen pasar por referencia en C ++, por lo que es un error fácil de cometer. El código es casi seguro incorrecto, pero es incorrecto de una manera no obvia. Así que este es un buen candidato para una advertencia. (Por eso es tan desafortunado que esta es una advertencia en C # 2 pero no en C # 3, debido a un error que introdujimos).

¿Por qué el compilador de C # ni siquiera se queja con una advertencia en este código? :

if (this == null) { // ... }

Obviamente la condición nunca será satisfecha ..


Aunque el código de abajo es un bastardo, todavía es todo C #. y si llama a Bar, recibirá una InvalidOperationException con el mensaje null.

public class Foo { static Action squareIt; static Foo() { var method = new DynamicMethod( "TryItForReal", typeof(void), Type.EmptyTypes, typeof(Foo).Module); ILGenerator il = method.GetILGenerator(); il.Emit(OpCodes.Ldnull); il.Emit(OpCodes.Call, typeof(Foo).GetMethod("tryit")); il.Emit(OpCodes.Ret); squareIt = (Action)method.CreateDelegate(typeof(Action)); } public void tryit() { if (this == null) { throw new InvalidOperationException("Was null"); } } public void Bar() { squareIt(); } }


En realidad, la condición realmente puede satisfacerse , al menos en Visual Studio 2008. Han corregido este comportamiento en VS 2010, pero no es del todo inconcebible que pueda haber otra forma de crear tal condición.

(tl; versión dr: en C # 3.5, es legal hacer referencia a this desde una función anónima pasada como un argumento de constructor, pero si realmente intenta usarlo, encontrará que this es null ).


Esto también está en línea con otras advertencias que C # hace (o no hace para eso) como:

if(true) or if(1 == 1)

Estos también siempre tendrán el mismo resultado, no importa qué.


Porque podría anular el operator == para devolver verdadero para ese caso.

public class Foo { public void Test() { Console.WriteLine(this == null); } public static bool operator ==(Foo a, Foo b) { return true; } public static bool operator !=(Foo a, Foo b) { return true; } }

Ejecutando un new Foo().Test() imprimirá "Verdadero" en la consola.

La otra pregunta aquí es: ¿por qué el compilador no emite una advertencia para ReferenceEquals(this, null) ? Desde la parte inferior del enlace de arriba:

Un error común en las sobrecargas del operator == es usar (a == b) , (a == null) o (b == null) para verificar la igualdad de referencia. Esto, en cambio, resulta en una llamada al operator == sobrecargado operator == , causando un bucle infinito. Use ReferenceEquals o emita el tipo a Object para evitar el bucle.

Eso podría ser respondido por la respuesta de @Aaronaught. Y también es por eso que debería hacer (object)x == null o ReferenceEquals(x, null) , no hacer un simple x == null , cuando está buscando referencias nulas. A menos que, por supuesto, esté seguro de que el operador == no está sobrecargado.