¿Por qué puedo probar un genérico para nulo cuando puede no ser anulable o no ser un objeto?
La pregunta está mal planteada; el valor de cualquier expresión de tipo genérico siempre será un objeto o una referencia nula en tiempo de ejecución. Creo que quería preguntar:
¿Por qué puedo probar un genérico para nulo cuando su tipo de tiempo de ejecución podría no ser ni un tipo de valor que admite nulos ni un tipo de referencia?
El C# garantías de especificación que la comparación de igualdad frente a la nula literal es legal en la sección 7.6.10, que cito aquí para su conveniencia:
Si un operando de un tipo de parámetro de tipo T se compara con nulo, y el tipo de tiempo de ejecución de T es un tipo de valor, el resultado de la comparación es falso. [...] La construcción x == null
está permitida aunque T podría representar un tipo de valor, y el resultado simplemente se define como falso cuando T es un tipo de valor.
Tenga en cuenta que hay un pequeño error en la especificación aquí; la última oración debe terminar "tipo de valor no admitible".
Nunca estoy seguro de haber respondido realmente un "¿por qué?" pregunta satisfactoriamente o no. Si eso no es satisfactorio, intente hacer una pregunta más específica.
¿No debería el compilador advertirme que uso null?
No. ¿Por qué cree que el compilador debería advertirle sobre el uso correcto de una característica del idioma?
¿Está usando la conversión de boxeo a un objeto para hacer una prueba de nulo aquí?
Bueno, eso es un punto delicado. Por un lado, la sección 7.6.10 estados:
Los operadores de igualdad de tipo de referencia predefinidos nunca causan las operaciones de boxeo que se produzca por sus operandos. No tendría sentido realizar tales operaciones de boxeo, ya que las referencias a las nuevas instancias en caja asignadas necesariamente diferirían de todas las otras referencias.
Sin embargo, cuando generamos IL para la comparación de una T genérica a null, que, por supuesto, en realidad sí generan una caja de T, una carga de nulo, y una comparación. El jitter puede ser lo suficientemente inteligente como para eludir la asignación real de memoria del boxeo; si T es un tipo de referencia, no necesita boxeo; si es un tipo de valor que admite valores NULL, se puede convertir en una llamada para verificar la propiedad HasValue, y si se trata de un tipo de valor que no admite nulos, el boxeo y la verificación pueden ser convertido en simplemente generar "falso". No sé exactamente qué hacen las diferentes implementaciones del compilador jit; si estás interesado, ¡mira y mira!
Visual Studio 2012 pone una línea ondulada debajo del operador == y dice "Posible comparación del tipo de valor con nulo". –