Si creo un bool dentro de mi clase, solo algo así bool check
, el valor predeterminado es falso.
Cuando creo el mismo bool dentro de mi método, bool check
(en lugar de dentro de la clase), aparece el error "uso de verificación de variable local no asignada". ¿Por qué?
c#
language-design
local-variables
nachime
fuente
fuente
Respuestas:
Las respuestas de Yuval y David son básicamente correctas; Resumiendo:
Un comentarista de la respuesta de David pregunta por qué es imposible detectar el uso de un campo no asignado a través del análisis estático; Este es el punto que quiero ampliar en esta respuesta.
En primer lugar, para cualquier variable, local o de otro tipo, en la práctica es imposible determinar exactamente si una variable está asignada o no asignada. Considerar:
La pregunta "¿está asignada x?" es equivalente a "¿M () devuelve verdadero?" Ahora, supongamos que M () devuelve verdadero si el último teorema de Fermat es verdadero para todos los enteros de menos de once mil millones, y falso en caso contrario. Para determinar si x está definitivamente asignado, el compilador esencialmente debe producir una prueba del último teorema de Fermat. El compilador no es tan inteligente.
Entonces, lo que hace el compilador para los locales es implementar un algoritmo que es rápido y sobreestima cuando un local no está asignado definitivamente. Es decir, tiene algunos falsos positivos, donde dice "No puedo probar que este local esté asignado" a pesar de que usted y yo sabemos que es así. Por ejemplo:
Supongamos que N () devuelve un entero. Usted y yo sabemos que N () * 0 será 0, pero el compilador no lo sabe. (Nota: el compilador de C # 2.0 lo sabía, pero eliminé esa optimización, ya que la especificación no dice que el compilador lo sepa).
Muy bien, ¿qué sabemos hasta ahora? No es práctico que los locales obtengan una respuesta exacta, pero podemos sobrestimar la no asignación a bajo costo y obtener un resultado bastante bueno que se equivoque del lado de "hacer que arregle su programa poco claro". Eso es bueno. ¿Por qué no hacer lo mismo para los campos? Es decir, ¿hacer un comprobador de asignación definitiva que sobreestime a bajo costo?
Bueno, ¿de cuántas maneras hay que inicializar un local? Se puede asignar dentro del texto del método. Se puede asignar dentro de una lambda en el texto del método; que lambda nunca se invoque, por lo que esas asignaciones no son relevantes. O se puede pasar como "fuera" a otro método, en cuyo punto podemos suponer que se asigna cuando el método vuelve normalmente. Esos son puntos muy claros en los que se asigna el local, y están allí en el mismo método en que se declara el local . La determinación de la asignación definitiva para locales requiere solo un análisis local . Los métodos tienden a ser cortos, mucho menos de un millón de líneas de código en un método, por lo que analizar todo el método es bastante rápido.
¿Y qué hay de los campos? Los campos se pueden inicializar en un constructor, por supuesto. O un inicializador de campo. O el constructor puede llamar a un método de instancia que inicializa los campos. O el constructor puede llamar a un método virtual que inicializa los campos. O el constructor puede llamar a un método en otra clase , que podría estar en una biblioteca , que inicializa los campos. Los campos estáticos se pueden inicializar en constructores estáticos. Los campos estáticos pueden ser inicializados por otros constructores estáticos.
Esencialmente, el inicializador de un campo podría estar en cualquier parte del programa completo , incluidos los métodos virtuales internos que se declararán en bibliotecas que aún no se han escrito :
¿Es un error compilar esta biblioteca? En caso afirmativo, ¿cómo se supone que BarCorp corrige el error? Al asignar un valor predeterminado a x? Pero eso es lo que el compilador ya hace.
Supongamos que esta biblioteca es legal. Si FooCorp escribe
¿Es eso un error? ¿Cómo se supone que el compilador se da cuenta de eso? La única forma es hacer un análisis completo del programa que rastree la inicialización estática de cada campo en cada ruta posible a través del programa , incluidas las rutas que involucran la elección de métodos virtuales en tiempo de ejecución . Este problema puede ser arbitrariamente difícil ; Puede implicar la ejecución simulada de millones de rutas de control. El análisis de los flujos de control local toma microsegundos y depende del tamaño del método. El análisis de los flujos de control global puede llevar horas porque depende de la complejidad de cada método en el programa y todas las bibliotecas .
Entonces, ¿por qué no hacer un análisis más barato que no tenga que analizar todo el programa y que simplemente se sobreestime aún más severamente? Bueno, proponga un algoritmo que funcione que no dificulte demasiado escribir un programa correcto que realmente compila, y el equipo de diseño puede considerarlo. No conozco tal algoritmo.
Ahora, el comentarista sugiere "requerir que un constructor inicialice todos los campos". Esa no es una mala idea. De hecho, es una idea tan mala que C # ya tiene esa característica para estructuras . Se requiere un constructor de estructura para asignar definitivamente todos los campos para cuando el ctor regrese normalmente; El constructor predeterminado inicializa todos los campos a sus valores predeterminados.
¿Qué hay de las clases? Bueno, ¿cómo sabes que un constructor ha inicializado un campo ? El ctor podría llamar a un método virtual para inicializar los campos, y ahora estamos de vuelta en la misma posición en la que estábamos antes. Las estructuras no tienen clases derivadas; clases de poder. ¿Se requiere una biblioteca que contenga una clase abstracta para contener un constructor que inicialice todos sus campos? ¿Cómo sabe la clase abstracta a qué valores deben inicializarse los campos?
John sugiere simplemente prohibir los métodos de llamada en un ctor antes de que se inicialicen los campos. En resumen, nuestras opciones son:
El equipo de diseño eligió la tercera opción.
fuente
bool x;
sea equivalentebool x = false;
incluso dentro de un método ?Porque el compilador está tratando de evitar que cometas un error.
¿Inicializar su variable para
false
cambiar algo en esta ruta de ejecución particular? Probablemente no, considerarlodefault(bool)
es falso de todos modos, pero te obliga a ser consciente de que esto está sucediendo. El entorno .NET le impide acceder a "memoria basura", ya que inicializará cualquier valor a su valor predeterminado. Pero aún así, imagine que este es un tipo de referencia, y pasaría un valor no inicializado (nulo) a un método que espera un valor no nulo, y obtendría un NRE en tiempo de ejecución. El compilador simplemente está tratando de evitar eso, aceptando el hecho de que esto a veces puede resultar enbool b = false
declaraciones.Eric Lippert habla sobre esto en una publicación de blog :
¿Por qué esto no se aplica a un campo de clase? Bueno, supongo que la línea tuvo que dibujarse en algún lugar, y la inicialización de las variables locales es mucho más fácil de diagnosticar y acertar, en comparación con los campos de clase. El compilador podría hacer esto, pero piense en todas las posibles comprobaciones que necesitaría realizar (donde algunas de ellas son independientes del código de la clase en sí) para evaluar si cada campo de una clase se inicializa. No soy un diseñador de compiladores, pero estoy seguro de que sería definitivamente más difícil, ya que hay muchos casos que se tienen en cuenta y que también deben hacerse de manera oportuna . Para cada característica que tenga que diseñar, escribir, probar e implementar, y el valor de implementar esto en lugar del esfuerzo realizado sería inútil y complicado.
fuente
La respuesta corta es que el compilador puede detectar el código de acceso a variables locales no inicializadas de manera confiable, utilizando análisis estático. Mientras que este no es el caso de los campos. Entonces el compilador aplica el primer caso, pero no el segundo.
Esto no es más que una decisión de diseño del lenguaje C #, como lo explicó Eric Lippert . El entorno CLR y .NET no lo requieren. VB.NET, por ejemplo, compilará perfectamente con variables locales no inicializadas, y en realidad el CLR inicializa todas las variables no inicializadas a los valores predeterminados.
Lo mismo podría ocurrir con C #, pero los diseñadores de idiomas decidieron no hacerlo. La razón es que las variables inicializadas son una gran fuente de errores y, por lo tanto, al ordenar la inicialización, el compilador ayuda a reducir los errores accidentales.
Entonces, ¿por qué esta inicialización explícita obligatoria no ocurre con los campos dentro de una clase? Simplemente porque esa inicialización explícita podría ocurrir durante la construcción, a través de una propiedad llamada por un inicializador de objeto, o incluso por un método llamado mucho después del evento. El compilador no puede usar el análisis estático para determinar si cada ruta posible a través del código lleva a que la variable se inicialice explícitamente ante nosotros. Hacerlo mal sería molesto, ya que el desarrollador podría quedarse con un código válido que no se compilará. Por lo tanto, C # no lo impone en absoluto y se deja que CLR inicialice automáticamente los campos a un valor predeterminado si no se establece explícitamente.
La aplicación de C # de la inicialización de variables locales es limitada, lo que a menudo atrapa a los desarrolladores. Considere las siguientes cuatro líneas de código:
La segunda línea de código no se compilará, ya que está tratando de leer una variable de cadena no inicializada. Sin embargo, la cuarta línea de código se compila muy bien, como
array
se ha inicializado, pero solo con los valores predeterminados. Como el valor predeterminado de una cadena es nulo, obtenemos una excepción en tiempo de ejecución. Cualquiera que haya pasado tiempo aquí en Stack Overflow sabrá que esta inconsistencia de inicialización explícita / implícita conduce a una gran cantidad de "¿Por qué obtengo un error de" Referencia de objeto no establecida en una instancia de un objeto "? preguntasfuente
public interface I1 { string str {get;set;} }
y un métodoint f(I1 value) { return value.str.Length; }
. Si esto existe en una biblioteca, el compilador no puede saber a qué se vinculará esa biblioteca, por lo tanto, siset
se habrá llamado antesget
, el campo de respaldo podría no haberse inicializado explícitamente, pero debe compilar dicho código.f
. Se generaría al compilar los constructores. Si deja un constructor con un campo posiblemente sin inicializar, eso sería un error. También podría tener que haber restricciones para llamar a métodos de clase y captadores antes de que se inicialicen todos los campos.Buenas respuestas arriba, pero pensé que publicaría una respuesta mucho más simple / más corta para que las personas sean perezosas para leer una larga (como yo).
Clase
La propiedad
Boo
puede o no haberse inicializado en el constructor. Entonces, cuando lo encuentrareturn Boo;
, no asume que se ha inicializado. Simplemente suprime el error.Función
Los
{ }
caracteres definen el alcance de un bloque de código. El compilador recorre las ramas de estos{ }
bloques haciendo un seguimiento de las cosas. Se puede decir fácilmente queBoo
no se inicializó. El error entonces se dispara.¿Por qué existe el error?
El error se introdujo para reducir la cantidad de líneas de código necesarias para que el código fuente sea seguro. Sin el error, lo anterior se vería así.
Del manual:
Referencia: https://msdn.microsoft.com/en-us/library/4y7h161d.aspx
fuente