Habiendo leído esta pregunta en HNQ, seguí leyendo sobre los tipos de referencia anulables en C # 8 , e hice algunos experimentos.
Soy muy consciente de que 9 de cada 10 veces, o incluso más a menudo, cuando alguien dice "¡Encontré un error del compilador!" esto es en realidad por diseño y su propio malentendido. Y desde que comencé a analizar esta característica solo hoy, claramente no la entiendo muy bien. Con esto fuera del camino, veamos este código:
#nullable enable
class Program
{
static void Main()
{
var s = "";
var b = s == null; // If you comment this line out, the warning on the line below disappears
var i = s.Length; // warning CS8602: Dereference of a possibly null reference
}
}
Después de leer la documentación que he vinculado anteriormente, esperaría que s == null
línea me avisara, después de todo s
es claramente no anulable, por lo que compararlo null
no tiene sentido.
En cambio, recibo una advertencia en la siguiente línea, y la advertencia dice que s
es posible una referencia nula, aunque, para un humano, es obvio que no lo es.
Además, la advertencia no se muestra si no nos comparamos s
con null
.
Busqué en Google y me encontré con un problema de GitHub , que resultó ser sobre algo completamente diferente, pero en el proceso tuve una conversación con un colaborador que me dio más información sobre este comportamiento (por ejemplo, "Las comprobaciones nulas son a menudo una forma útil de decirle al compilador que restablezca su inferencia previa sobre la nulabilidad de una variable " . Sin embargo, esto todavía me dejó con la pregunta principal sin respuesta.
En lugar de crear un nuevo problema de GitHub, y potencialmente tomar el tiempo de los contribuyentes del proyecto increíblemente ocupados, lo estoy exponiendo a la comunidad.
¿Podría explicarme qué está pasando y por qué? En particular, ¿por qué no se generan advertencias en la s == null
línea y por qué las tenemos CS8602
cuando no parece que una null
referencia sea posible aquí? Si la inferencia de nulabilidad no es a prueba de balas, como sugiere el hilo de GitHub vinculado, ¿cómo puede salir mal? ¿Cuáles serían algunos ejemplos de eso?
fuente
?
porques
no es anulable. No se anula, simplemente porque fuimos lo suficientemente tontos como para compararlonull
.Respuestas:
Esto es efectivamente un duplicado de la respuesta que @stuartd vinculó, por lo que no voy a entrar en detalles súper profundos aquí. Pero la raíz del asunto es que no se trata de un error de idioma ni de un compilador, sino de un comportamiento previsto tal como se implementó. Rastreamos el estado nulo de una variable. Cuando declara inicialmente la variable, ese estado es NotNull porque lo inicializa explícitamente con un valor que no es nulo. Pero no rastreamos de dónde vino ese NotNull. Esto, por ejemplo, es efectivamente un código equivalente:
En ambos casos, se prueba de forma explícita
s
paranull
. Tomamos esto como entrada para el análisis de flujo, tal como Mads respondió en esta pregunta: https://stackoverflow.com/a/59328672/2672518 . En esa respuesta, el resultado es que recibe una advertencia en la devolución. En este caso, la respuesta es que recibe una advertencia de que ha desreferenciado una referencia posiblemente nula.Sí, en realidad lo hace. Al compilador. Como humanos, podemos mirar este código y obviamente entender que no puede arrojar una excepción de referencia nula. Pero la forma en que se implementa el análisis de flujo anulable en el compilador, no puede. Discutimos algunas mejoras en este análisis donde agregamos estados adicionales basados en el origen del valor, pero decidimos que esto agregaba una gran complejidad a la implementación sin mucha ganancia, porque los únicos lugares donde Esto sería útil para casos como este, en los que el usuario inicializa una variable con un
new
valor constante o luego y luego la verifica denull
todos modos.fuente
s == null
no produce una advertencia?#nullable enable
;string s = "";s = null;
compila y funciona (todavía produce una advertencia) ¿cuáles son los beneficios de la implementación que permite asignar nulo a una "referencia no anulable" en un contexto de anulación nulo habilitado?s == null
. Quizás, por ejemplo, estás en un método público y quieres hacer la validación de parámetros. O, tal vez esté utilizando una biblioteca que anotó incorrectamente, y hasta que solucionen ese error, debe lidiar con nulo donde no se declaró. En cualquiera de esos casos, si advertimos, sería una mala experiencia. En cuanto a permitir la asignación: las anotaciones de variables locales son solo para leer. No afectan el tiempo de ejecución en absoluto. De hecho, ponemos todas esas advertencias en un único código de error para que pueda desactivarlas si desea reducir la pérdida de código.Felizmente adopté las referencias anulables de C # 8 tan pronto como estuvieron disponibles. Como estaba acostumbrado a usar la notación [NotNull] (etc.) de ReSharper, noté algunas diferencias entre los dos.
El compilador de C # puede ser engañado, pero tiende a errar por precaución (generalmente, no siempre).
Como referencia para futuros visitantes, estos son los escenarios por los que vi que el compilador estaba bastante confundido (supongo que todos estos casos son por diseño):
Sin embargo, permite usar alguna construcción para verificar la nulabilidad que también elimina la advertencia:
o atributos (aún geniales) (búscalos aquí ):
string?
es solo una cadena, seint?
convierte en unaNullable<int>
y obliga al compilador a manejarlas de maneras sustancialmente diferentes. También aquí, el compilador está eligiendo la ruta segura, lo que le obliga a especificar lo que espera:Resuelto dando restricciones:
Pero si no usamos restricciones y eliminamos el '?' de datos, todavía podemos poner valores nulos en él usando la palabra clave 'predeterminada':
El último me parece más complicado, ya que permite escribir código inseguro.
Espero que esto ayude a alguien.
fuente
ThrowIfNull(s);
me asegura ques
no es nulo). Además, el artículo explica cómo manejar genéricos no anulables , mientras mostraba cómo puede "engañar" al compilador, teniendo un valor nulo pero sin advertencia al respecto.DoesNotReturnIf(bool)
.DoesNotReturnIfNull(nullable)
.