2012-04-12 11 views
20

¿Por qué arrojar una excepción en el constructor da como resultado una referencia nula? Por ejemplo, si ejecutamos los códigos a continuación, el valor de teacher es nulo, mientras que st.teacher no (se crea un objeto de Teacher). ¿Por qué?¿Por qué arrojar una excepción en los resultados del constructor en una referencia nula?

using System; 

namespace ConsoleApplication1 
{ 
    class Program 
    { 
    static void Main(string[] args) 
    { 
     Test(); 
    } 

    private static void Test() 
    { 
     Teacher teacher = null; 
     Student st = new Student(); 
     try 
     { 
     teacher = new Teacher("", st); 
     } 
     catch (Exception e) 
     { 
     Console.WriteLine(e.Message); 
     } 
     Console.WriteLine((teacher == null)); // output True 
     Console.WriteLine((st.teacher == null)); // output False 
    } 
    } 

    class Teacher 
    { 
    public string name; 
    public Teacher(string name, Student student) 
    { 
     student.teacher = this; 
     if (name.Length < 5) 
     throw new ArgumentException("Name must be at least 5 characters long."); 
    } 
    } 

    class Student 
    { 
    public Teacher teacher; 
    } 

} 

Respuesta

37

El constructor nunca se completa, por lo tanto, la tarea nunca ocurre. No es que el constructor haya devuelto un valor nulo (o que haya un "objeto nulo"; no existe tal concepto). Es solo que nunca le asigna un nuevo valor a teacher, por lo que conserva su valor anterior.

Por ejemplo, si se utiliza:

Teacher teacher = new Teacher("This is valid", new Student()); 
Student st = new Student(); 
try 
{ 
    teacher = new Teacher("", st); 
} 
catch (... etc ...) 

... entonces usted todavía tiene el maestro "Esto es válido". La variable name todavía no se le asignará un valor en que Teacher objeto, sin embargo, como su Teacher constructor no se encuentra una línea como:

this.name = name; 
+0

gracias por la gran explicación, edité "objeto nulo" en la pregunta a "referencia nula". –

+0

Gran explicación, también acaba de demostrar que el objeto no inicializado en C# siempre contiene «nulo». Comencé a dudar de eso porque cuando traté de usar en Visual Studio un objeto que podría no ser inicializado en ciertas condiciones, pero de todos modos se verificó como "nulo", y luego se usó, el compilador mostró un error sobre la variable no inicializada. Después de inicializar explícitamente el objeto con «null», el error desapareció. Gracias a ti, ahora sé que es solo un error de Visual Studio. –

+4

@ Hi-Angel: No, no es un error. Es la diferencia entre un * campo * y una variable local. Un campo tiene un valor predeterminado y se puede usar sin haber sido configurado; una variable local * no puede leerse hasta que esté definitivamente asignada. –

3

Cuando lanza una excepción en un constructor, se rompe la construcción del objeto. Por lo tanto, nunca termina y, por lo tanto, no hay ningún objeto para devolver. De hecho, ese operador de asignación (teacher = new Teacher("", st);) nunca se ejecuta porque la excepción rompe la pila de llamadas.

Y el constructor Profesor todavía escribe una referencia a sí mismo (el objeto que se está construyendo) en la propiedad del objeto Estudiante. Pero nunca más debes intentar usar este objeto de Profesor, ya que no se ha construido. Puede dar lugar a un comportamiento indefinido.

12

Porque está revisando las referencias.

try 
    { 
    teacher = new Teacher("", st); //this line raises an exception 
            // so teacher REMAINS NULL. 
            // it's NOT ASSIGNED to NULL, 
            // but just NOT initialized. That is. 
    } 
    catch (Exception e) 
    { 
    Console.WriteLine(e.Message); 
    } 

pero

public Teacher(string name, Student student) 
{ 
    student.teacher = this; //st.Teacher is assigned BEFORE exception raised. 
    if (name.Length < 5) 
    throw new ArgumentException("Name must be at least 5 characters long."); 
} 
0

usted está lanzando la excepción después de la asignación '= student.teacher este ; // Esta línea se ejecuta if (name.Length < 5) // Esto está marcado y es verdadero en el caso especificado throw new ArgumentException ("El nombre debe tener al menos 5 caracteres de longitud."); // BAM: Exception arrojado aquí. '

El valor del profesor es nulo (como excepción lanzada antes de la finalización del constructor), mientras que st.teacher no lo es!

-1

El trabajo principal del constructor es inicializar el objeto. Si hay una excepción en la inicialización, entonces no tiene sentido tener un objeto que no se haya inicializado correctamente. Por lo tanto, lanzar una excepción desde un constructor da como resultado un objeto nulo.

+0

Esto no es correcto; no resulta en nada en absoluto, como se señala en otras respuestas. – Ashe

0

Si Foo es un tipo de referencia, la declaración Foo = new FooType(); construirá un objeto y, a continuación, después de que el constructor ha completado, almacenar una referencia en Foo. Si el constructor arroja una excepción, se saltará el código que almacenaría la referencia en Foo sin que se haya escrito Foo.

En los casos en que:

  • Una declaración como la anterior se produce dentro de un bloque try/catch
  • La declaración se puede llegar sin Foo haber sido escrito de antemano.
  • Foo variable local definida en un contexto que rodea el bloque catch.
  • Es posible que la ejecución que comienza en la captura alcance una instrucción que lea Foo sin haber escrito después del catch.

El compilador asumirá que este último intento de leer Foo podría ser ejecutado sin Foo haber sido escrito, y rechazará la compilación en ese caso. El compilador permite Foo para ser leído sin haber sido escrita, sin embargo, si:

  • Foo es un campo de clase, o un campo de una estructura almacenada en un campo de clase, un campo de una estructura almacena en un campo de una estructura almacenada en un campo de clase, etc.
  • Foo se pasa como un parámetro out a un método (escrito en un idioma distinto de C#) que no almacena nada, y la instrucción que dice foo solo ser alcanzable si el método ha retornado normalmente en lugar de a través de una excepción.

En el primer caso, Foo tendrá un valor definido de null. En este último caso, el valor de Foo probablemente será nulo la primera vez que se crea durante la ejecución de un método; si se vuelve a crear dentro de un ciclo, puede contener null o el último valor que se escribió después de la última vez que se creó; el estándar no es específico sobre lo que sucederá en esa situación.

Tenga en cuenta que si FooType tiene algo parecido a un constructor normal, Foo = new FooType(); nunca causaFoo para convertirse en un valor nulo si antes no lo era. Si la instrucción se completa normalmente, Foo tendrá una referencia a una instancia del tipo exacto FooType para el que no existía ninguna referencia en ningún lugar del universo; si arroja una excepción, no afectará al Foo de ninguna manera.

Cuestiones relacionadas