¿Por qué las variables locales requieren inicialización, pero los campos no?

Si creo un bool dentro de mi clase, algo así como bool check , por defecto 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é?

    Las respuestas de Yuval y David son básicamente correctas; Resumiendo:

    • El uso de una variable local no asignada es un posible error, y el comstackdor puede detectarlo a bajo costo.
    • El uso de un campo o elemento de matriz no asignado es menos probable que sea un error, y es más difícil detectar la condición en el comstackdor. Por lo tanto, el comstackdor no intenta detectar el uso de una variable no inicializada para los campos, y en cambio se basa en la inicialización al valor predeterminado para que el comportamiento del progtwig sea determinista.

    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 sobre el que quiero expandirme en esta respuesta.

    En primer lugar, para cualquier variable, local o de otro tipo, en la práctica es imposible determinar con exactitud si una variable está asignada o no asignada. Considerar:

     bool x; if (M()) x = true; Console.WriteLine(x); 

    La pregunta “¿está asignada x?” es equivalente a “does M () return true?” Ahora, supongamos que M () devuelve verdadero si el último teorema de Fermat es verdadero para todos los enteros inferiores a eleventy gajillion, y de lo contrario es falso. Para determinar si x se asigna definitivamente, el comstackdor esencialmente debe producir una prueba del último teorema de Fermat. El comstackdor no es tan inteligente.

    Entonces, lo que el comstackdor hace para los locales es implementar un algoritmo que es rápido , y sobreestima cuando un local no está definitivamente asignado. Es decir, tiene algunos falsos positivos, donde dice “No puedo probar que este local esté asignado”, aunque usted y yo sepamos que sí. Por ejemplo:

     bool x; if (N() * 0 == 0) x = true; Console.WriteLine(x); 

    Supongamos que N () devuelve un número entero. Tú y yo sabemos que N () * 0 será 0, pero el comstackdor no lo sabe. (Nota: el comstackdor C # 2.0 sí lo sabía, pero eliminé esa optimización, ya que la especificación no dice que el comstackdor lo sepa).

    De acuerdo, entonces, ¿qué sabemos hasta ahora? No es práctico que los lugareños obtengan una respuesta exacta, pero podemos sobrestimar la no-asignación a bajo costo y obtener un resultado bastante bueno que se equivoca del lado de “hacerle arreglar su progtwig poco claro”. Eso es bueno. ¿Por qué no hacer lo mismo para los campos? Es decir, ¿es un comprobador de asignación definido que se sobreestima de forma económica?

    Bueno, ¿cuántas formas hay para que se inicialice un local? Se puede asignar dentro del texto del método. Se puede asignar dentro de una lambda en el texto del método; ese lambda podría nunca invocarse, por lo que esas asignaciones no son relevantes. O puede pasarse como “fuera” a otro método, en cuyo punto podemos suponer que se asigna cuando el método retorna normalmente. Esos son puntos muy claros en los que se asigna el local, y están justo allí en el mismo método que el local se declara . La determinación de la asignación definitiva para los locales solo requiere análisis local . Los métodos tienden a ser cortos, mucho menos que 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 ahora 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 initailiza 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 lugar del progtwig completo , incluidos los métodos virtuales internos que se declararán en las bibliotecas que aún no se han escrito :

     // Library written by BarCorp public abstract class Bar { // Derived class is responsible for initializing x. protected int x; protected abstract void InitializeX(); public void M() { InitializeX(); Console.WriteLine(x); } } 

    ¿Es un error comstackr 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 comstackdor ya hace.

    Supongamos que esta biblioteca es legal. Si FooCorp escribe

     public class Foo : Bar { protected override void InitializeX() { } } 

    ¿es eso un error? ¿Cómo se supone que el comstackdor se da cuenta de eso? La única forma es realizar un análisis completo del progtwig que rastree la inicialización estática de cada campo en cada ruta posible a través del progtwig , incluidas las rutas que implican la elección de métodos virtuales en el 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 lleva microsegundos y depende del tamaño del método. El análisis de los flujos de control global puede tomar horas, ya que depende de la complejidad de cada método en el progtwig y todas las bibliotecas .

    Entonces, ¿por qué no hacer un análisis más barato que no tiene que analizar todo el progtwig, y ​​simplemente sobreestimar aún más severamente? Bueno, proponga un algoritmo que funcione que no haga demasiado difícil escribir un progtwig correcto que realmente compile, y el equipo de diseño puede considerarlo. No sé de ningún algoritmo de ese tipo.

    Ahora, el comentarista sugiere “requiere 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 las estructuras . Se requiere un constructor de estructuras para asignar definitivamente todos los campos cuando el contador retorna 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 coordinador 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; las clases pueden. ¿Se requiere que una biblioteca que contiene una clase abstracta contenga 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 los campos sean inicializados. Entonces, resumiendo, nuestras opciones son:

    • Haga que los modismos de progtwigción comunes, seguros y de uso frecuente sean ilegales.
    • Haga un costoso análisis de todo el progtwig que hace que la comstackción tome horas para buscar errores que probablemente no estén allí.
    • Confiar en la inicialización automática a los valores predeterminados.

    El equipo de diseño eligió la tercera opción.

    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é?

    Porque el comstackdor intenta evitar que cometas un error.

    ¿Inicializar su variable a false cambia algo en esta ruta particular de ejecución? Probablemente no, considerando que el default(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 era un tipo de referencia, y pasaría un valor no inicializado (nulo) a un método esperando un no nulo, y obtendría un NRE en tiempo de ejecución. El comstackdor simplemente está tratando de evitar eso, aceptando el hecho de que esto a veces puede dar como resultado bool b = false declaraciones bool b = false .

    Eric Lippert habla de esto en una publicación de blog :

    La razón por la que queremos que esto sea ilegal no es, como mucha gente cree, porque la variable local se va a inicializar en la basura y queremos protegerlo de la basura. De hecho, de hecho, inicializamos automáticamente locales a sus valores predeterminados. (Aunque los lenguajes de progtwigción C y C ++ no lo hacen, y le permitirán leer basura desde un local no inicializado). Más bien, es porque la existencia de una ruta de código de este tipo es probablemente un error, y queremos arrojarlo en el pozo de calidad; deberías tener que trabajar duro para escribir ese error.

    ¿Por qué esto no se aplica a un campo de clase? Bueno, supongo que la línea tuvo que dibujarse en alguna parte, y la inicialización de variables locales es mucho más fácil de diagnosticar y corregir, a diferencia de los campos de clase. El comstackdor podría hacer esto, pero piense en todas las comprobaciones posibles que debería realizar (algunas de ellas son independientes del código de clase) para evaluar si cada campo de una clase se inicializa. No soy diseñador de comstackdores, pero estoy seguro de que sería definitivamente más difícil ya que hay muchos casos que se tienen en cuenta, y también se debe hacer de manera oportuna . Para cada característica que tiene que diseñar, escribir, probar y desplegar, y el valor de implementar esto en oposición al esfuerzo puesto, no sería digno ni complicado.

    ¿Por qué las variables locales requieren inicialización, pero los campos no?

    La respuesta corta es que el código que accede a las variables locales no inicializadas puede ser detectado por el comstackdor de manera confiable, utilizando el análisis estático. Mientras que este no es el caso de los campos. Entonces el comstackdor aplica el primer caso, pero no el segundo.

    ¿Por qué las variables locales requieren inicialización?

    Esto no es más que una decisión de diseño del lenguaje C #, como lo explica Eric Lippert . El CLR y el entorno .NET no lo requieren. VB.NET, por ejemplo, comstackrá muy bien 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 del lenguaje eligieron no hacerlo. La razón es que las variables inicializadas son una gran fuente de errores y, al ordenar la inicialización, el comstackdor ayuda a reducir los errores accidentales.

    ¿Por qué los campos no requieren inicialización?

    Entonces, ¿por qué esta inicialización obligatoria y explícita 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 que es llamada por un inicializador de objetos, o incluso por un método que se llama mucho después del evento. El comstackdor 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 antes que nosotros. Hacerlo mal sería molesto, ya que el desarrollador podría quedarse con un código válido que no comstackrá. Entonces C # no lo impone en absoluto y CLR se deja inicializar automáticamente los campos a un valor predeterminado si no se establece explícitamente.

    ¿Qué pasa con los tipos de colección?

    La aplicación de C # de la inicialización de variable local es limitada, lo que a menudo atrapa a los desarrolladores. Considere las siguientes cuatro líneas de código:

     string str; var len1 = str.Length; var array = new string[10]; var len2 = array[0].Length; 

    La segunda línea de código no se comstackrá, ya que está tratando de leer una variable de cadena no inicializada. Sin embargo, la cuarta línea de código comstack bien, ya que la 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 incoherencia de inicialización explícita / implícita conduce a una gran cantidad de “¿Por qué recibo una” Referencia de objeto no configurada como instancia de un objeto “error”? preguntas

    Buenas respuestas arriba, pero pensé que publicaría una respuesta mucho más simple / corta para que las personas que se relajan puedan leer una larga (como yo).

    Clase

     class Foo { private string Boo; public Foo() { /** bla bla bla **/ } public string DoSomething() { return Boo; } } 

    Property Boo puede haberse inicializado o no en el constructor. Entonces, cuando encuentra el return Boo; no supone que ha sido inicializado. Simplemente suprime el error.

    Función

     public string Foo() { string Boo; return Boo; // triggers error } 

    Los { } caracteres definen el scope de un bloque de código. El comstackdor recorre las twigs de estos { } bloques para hacer un seguimiento de las cosas. Se puede decir fácilmente que Boo no se inicializó. El error se desencadena.

    ¿Por qué existe el error?

    El error fue introducido para reducir la cantidad de líneas de código requeridas para hacer que el código fuente sea seguro. Sin el error, lo anterior se vería así.

     public string Foo() { string Boo; /* bla bla bla */ if(Boo == null) { return ""; } return Boo; } 

    Del manual:

    El comstackdor C # no permite el uso de variables no inicializadas. Si el comstackdor detecta el uso de una variable que podría no haberse inicializado, genera el error de comstackción CS0165. Para obtener más información, vea Campos (Guía de progtwigción de C #). Tenga en cuenta que este error se genera cuando el comstackdor encuentra un constructo que podría resultar en el uso de una variable no asignada, incluso si su código particular no lo hace. Esto evita la necesidad de reglas demasiado complejas para una asignación definida.

    Referencia: https://msdn.microsoft.com/en-us/library/4y7h161d.aspx