¿Por qué C # le permite 'lanzar nulo'?

85

Al escribir un código de manejo de excepciones particularmente complejo, alguien preguntó, ¿no necesita asegurarse de que su objeto de excepción no sea nulo? Y dije, por supuesto que no, pero luego decidí intentarlo. Aparentemente, puede arrojar un valor nulo, pero aún se convierte en una excepción en algún lugar.

¿Por qué está permitido esto?

throw null;

En este fragmento, afortunadamente 'ex' no es nulo, pero ¿podría alguna vez serlo?

try
{
  throw null;
}
catch (Exception ex)
{
  //can ex ever be null?

  //thankfully, it isn't null, but is
  //ex is System.NullReferenceException
}
sofismo
fuente
8
¿Está seguro de que no está viendo una excepción de .NET (referencia nula) lanzada sobre su propio lanzamiento?
micahtan
4
Uno pensaría que el compilador al menos generaría una advertencia acerca de intentar "lanzar nulo" directamente;
Andy White
No, no estoy seguro. El marco podría muy bien estar tratando de hacer algo con mi objeto y cuando es nula, el marco lanza una "Excepción nula referencia" .. pero en última instancia, quiero estar seguro de que "ex" no puede ser nulo
chiste
1
@Andy: Una advertencia puede tener sentido para otras situaciones en el idioma, pero para throwuna declaración cuyo propósito es lanzar una excepción en primer lugar, una advertencia no agrega mucho valor.
Mehrdad Afshari
@Mehrdad: sí, pero dudo que algún desarrollador "arroje nulo" a propósito y quiera ver una NullReferenceException. Tal vez debería ser un error de compilación completo, como una comparación = incorrecta en una declaración if.
Andy White

Respuestas:

96

Porque la especificación del lenguaje espera una expresión de tipo System.Exceptionallí (por lo tanto, nulles válida en ese contexto) y no restringe esta expresión para que no sea nula. En general, no hay forma de que pueda detectar si el valor de esa expresión es nullo no. Tendría que resolver el problema de la detención. El tiempo de ejecución tendrá que ocuparse del nullcaso de todos modos. Ver:

Exception ex = null;
if (conditionThatDependsOnSomeInput) 
    ex = new Exception();
throw ex; 

Por supuesto, podrían hacer que el caso específico de arrojar lo nullliteral sea inválido, pero eso no ayudaría mucho, entonces, ¿por qué desperdiciar espacio de especificación y reducir la consistencia para obtener pocos beneficios?

Descargo de responsabilidad (antes de que Eric Lippert me abofetee): esta es mi propia especulación sobre el razonamiento detrás de esta decisión de diseño. Por supuesto, no he estado en la reunión de diseño;)


La respuesta a su segunda pregunta, si una variable de expresión capturada dentro de una cláusula catch puede ser nula alguna vez: si bien la especificación de C # nullno dice nada sobre si otros lenguajes pueden hacer que se propague una excepción, define la forma en que se propagan las excepciones:

Las cláusulas de captura, si las hay, se examinan en orden de aparición para localizar un manejador adecuado para la excepción. La primera cláusula catch que especifica el tipo de excepción o un tipo base del tipo de excepción se considera una coincidencia. Una cláusula catch general se considera una coincidencia para cualquier tipo de excepción. [...]

Porque null, la declaración en negrita es falsa. Entonces, si bien se basa puramente en lo que dice la especificación C #, no podemos decir que el tiempo de ejecución subyacente nunca arrojará un valor nulo, podemos estar seguros de que incluso si ese es el caso, solo lo manejará el genéricocatch {} cláusula .

Para las implementaciones de C # en la CLI, podemos consultar la especificación ECMA 335. Ese documento define todas las excepciones que la CLI lanza internamente (ninguna de las cuales lo es null) y menciona que los objetos de excepción definidos por el usuario son lanzados por la throwinstrucción. La descripción de esa instrucción es prácticamente idéntica a la instrucción C # throw(excepto que no restringe el tipo de objeto a System.Exception):

Descripción:

La throwinstrucción arroja el objeto de excepción (tipo O) en la pila y vacía la pila. Para obtener detalles sobre el mecanismo de excepción, consulte la Partición I.
[Nota: Aunque la CLI permite que se lance cualquier objeto, la CLS describe una clase de excepción específica que se utilizará para la interoperabilidad de idiomas. nota final]

Excepciones:

System.NullReferenceExceptionse lanza si objes null.

Exactitud:

El CIL correcto asegura que el objeto sea siempre nullo una referencia de objeto (es decir, de tipo O).

Creo que estos son suficientes para concluir que las excepciones capturadas nunca lo son null.

Mehrdad Afshari
fuente
Supongo que lo mejor que podría hacer sería prohibir frases explícitas como throw null;.
FrustratedWithFormsDesigner
7
En realidad, es completamente posible (en CLR) que se lance un objeto que no herede de System.Exception. Hasta donde yo sé, no puede hacerlo en C #, pero se puede hacer a través de IL, C ++ / CLR, etc. Consulte msdn.microsoft.com/en-us/library/ms404228.aspx para obtener más información.
tecnófilo
@tecnófilo: Sí. Estoy hablando del idioma aquí. En C #, no es posible lanzar una excepción de otros tipos, pero es posible detectarla usando una catch { }cláusula genérica .
Mehrdad Afshari
1
Si bien no tendría sentido que un compilador se negara a aceptar un throwcuyo argumento podría ser un valor nulo, eso no implicaría que throw nulltendría que ser legal. Un compilador podría insistir en que un throwargumento tenga un tipo de clase discernible. Una expresión como (System.InvalidOperationException)nulldebería ser válida en tiempo de compilación (ejecutarla debería causar a NullReferenceException) pero eso no significa que una expresión sin tipo nulldebería ser aceptable.
supercat
@supercat: Eso es cierto, pero el nulo en este caso se escribe implícitamente como Exception (porque se usa en un contexto donde se requiere Exception). null no es válido en tiempo de ejecución, por lo que se lanza NullReferenceException (como se indica en la respuesta de Anon.). La excepción lanzada no es la de la declaración 'throw'.
John B. Lambe
29

Aparentemente, puede arrojar un valor nulo, pero aún así se convierte en una excepción en algún lugar.

Intentando lanzar un null objeto da como resultado una excepción de referencia nula (sin ninguna relación).

Preguntar por qué se le permite lanzar nulles como preguntar por qué se le permite hacer esto:

object o = null;
o.ToString();
Luego.
fuente
5

Tomado de aquí :

Si usa esta expresión en su código C #, arrojará una NullReferenceException. Esto se debe a que la instrucción throw necesita un objeto de tipo Exception como parámetro único. Pero este mismo objeto es nulo en mi ejemplo.

Fitzchak Yitzchaki
fuente
5

Si bien puede que no sea posible arrojar un valor nulo en C # porque el lanzamiento lo detectará y lo convertirá en una NullReferenceException, ES posible recibir un valor nulo ... Sucede que estoy recibiendo eso en este momento, lo que hace que mi captura (que no fue esperando que 'ex' sea nulo) para experimentar una excepción de referencia nula que luego hace que mi aplicación muera (ya que esa fue la última captura).

Entonces, aunque no podemos arrojar un valor nulo desde C #, el inframundo puede arrojar un valor nulo, por lo que es mejor que su captura más externa (Excepción ex) esté preparada para recibirla. Solo para tu información.

Brian Kennedy
fuente
3
Interesante. ¿Podría publicar algún código o tal vez eludir lo que se encuentra en su profundidad debajo que está haciendo esto?
Peter Lillevold
No puedo decirte si es un error de CLR ... es difícil rastrear qué en las entrañas de .NET arrojó nulo y por qué, dado que no obtienes una excepción con la pila de llamadas o cualquier otra pista para continuar. Solo sé que mi captura más externa ahora verifica el argumento de excepción nula antes de usarlo.
Brian Kennedy
3
Sospecho que ES un error CLR, o debido a un código incorrecto no verificable. El CIL correcto solo arrojará un objeto no nulo o nulo (que se convierte en una NullReferenceException).
Demi
También estoy usando un servicio que devuelve una excepción nula. ¿Alguna vez descubrió cómo se lanzaba la excepción nula?
themiDdlest
2

Creo que tal vez no pueda; cuando intenta lanzar un valor nulo, no puede, por lo que hace lo que debería en un caso de error, que es lanzar una excepción de referencia nula. Entonces, en realidad, no está lanzando el nulo, no está lanzando el nulo, lo que resulta en un lanzamiento.

Steve Cooper
fuente
2

Intentando responder "... afortunadamente 'ex' no es nulo, pero ¿podría serlo alguna vez?":

Dado que podría decirse que no podemos lanzar excepciones que sean nulas, una cláusula catch nunca tendrá que detectar una excepción que sea nula. Por tanto, ex nunca podría ser nulo.

Veo ahora que esta pregunta ya se ha hecho .

Peter Lillevold
fuente
@Brian Kennedy nos dice en su comentario anterior que podemos atrapar nulos.
ProfK
@ Tvde1 - Creo que la distinción aquí es que, sí, puede ejecutar throw null. Pero eso no arrojará una excepción que sea nula . Más bien, debido a que throw nullintenta invocar métodos en una referencia nula, esto posteriormente obliga al tiempo de ejecución a lanzar una instancia no nula de NullReferenceException.
Peter Lillevold
1

Recuerde que una excepción incluye detalles sobre dónde se lanza la excepción. Dado que el constructor no tiene idea de dónde se va a lanzar, entonces solo tiene sentido que el método de lanzamiento inyecte esos detalles en el objeto en el punto en el que se encuentra el lanzamiento. En otras palabras, CLR está intentando inyectar datos en nulo, lo que desencadena una NullReferenceException.

No estoy seguro de si esto es exactamente lo que está sucediendo, pero explica el fenómeno.

Suponiendo que esto sea cierto (y no puedo pensar en una mejor manera de hacer que ex sea nulo que arrojar un valor nulo), eso significaría que ex no puede ser nulo.

Guvante
fuente
0

En c # más antiguo:

Considere esta sintaxis:

public void Add<T> ( T item ) => throw (hashSet.Add ( item ) ? null : new Exception ( "The item already exists" ));

Creo que es mucho más corto que esto:

public void Add<T> ( T item )
{
    if (!hashSet.Add ( item ))
        throw new Exception ( "The item already exists" );
}
Arutyun Enfendzhyan
fuente
¿Qué nos dice esto?
bornfromanegg
Por cierto, no estoy seguro de cuál es su definición de más corto, pero su segundo ejemplo contiene menos caracteres.
bornfromanegg
@bornfromanegg no el primero está insertado, por lo que definitivamente es más corto. Lo que estaba tratando de decir es que puede arrojar un error según la condición. Tradicionalmente, a u le gustaría el segundo ejemplo, pero dado que "lanzar nulo" no arroja nada, puede incluir el segundo ejemplo en línea para que parezca el primer ejemplo. Además, el primer ejemplo tiene 8 caracteres menos que el segundo
Arutyun Enfendzhyan
"Throw null" lanza una excepción NullReference. Lo que significa que su primer ejemplo siempre arrojará una excepción.
bornfromanegg
sí, lamentablemente ahora lo hace. Pero no lo hizo antes
Arutyun Enfendzhyan