为什么在构造函数中抛出exception导致空引用?

为什么在构造函数中抛出exception导致空引用? 例如,如果我们运行下面的代码,则teacher的值为null,而st.teacher则不是(创建了Teacher对象)。 为什么?

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; } } 

构造函数永远不会完成,因此赋值永远不会发生。 它不是从构造函数返回null(或者有一个“null对象” – 没有这样的概念)。 只是你永远不会给teacher分配一个新值,所以它保留了以前的值。

例如,如果您使用:

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

…那么你仍然会有“这是有效的”老师。 但是, name变量仍然不会在该Teacher对象中分配值,因为您的Teacher构造函数缺少一行,例如:

 this.name = name; 

因为你正在检查参考

  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 ); } 

 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." ); } 

在构造函数中抛出exception时,会破坏对象的构造。 所以它永远不会完成,因此没有回报的对象。 实际上,由于exception中断了调用堆栈,因此永远不会执行该赋值运算符( teacher = new Teacher( "", st ); )。

而Teacher构造函数仍然将对自身(正在构造的对象)的引用写入Student对象的属性。 但是之后你永远不应该尝试使用这个教师对象,因为它尚未构建。 它可能导致未定义的行为。

你在作业’student.teacher = this之后抛出exception; //如果(name.Length <5)执行此行//这被检查并且在指定的情况下为true抛出新的ArgumentException(“名称长度必须至少为5个字符。”); // BAM:此处抛出异常“。

因此,teacher的值为null(在构造函数完成之前抛出的exception),而st.teacher则不是!

如果Foo是引用类型,则语句Foo = new FooType(); 将构造一个对象,然后, 在构造函数完成后 ,将引用存储到Foo 。 如果构造函数抛出exception,则将跳过将引用存储到Foo的代码,而不写入Foo

如果:

  • 上面的语句发生在try / catch块中
  • 声明可以在事先没有写过Foo情况下达成。
  • Foocatch块周围的上下文中定义的局部变量。
  • 从catch开始执行可能会到达一个语句,该语句读取Foo而不会在catch之后写入。

编译器将假设后者尝试读取Foo可以在没有写入Foo情况下执行,并且在这种情况下将拒绝编译。 编译器将允许在未编写的情况下读取Foo ,但是,如果:

  • Foo是类字段,或存储在类字段中的结构的字段,存储在类字段中存储的结构的字段中的结构的字段等。
  • Foo作为out参数传递给一个方法(用C#以外的语言编写),该方法不存储任何内容,只有方法正常返回而不是exception才能读取读取foo的语句。

在前一种情况下, Foo将具有null的定义值。 在后一种情况下, Foo值在第一次在方法执行期间创建时可能为null; 如果在循环中重新创建,它可能包含null或在最后一次创建之后写入的最后一个值; 该标准并未具体说明在这种情况下会发生什么。

请注意,如果FooType具有类似于普通构造函数的任何内容,则Foo = new FooType(); 如果不是之前,永远不会导致 Foo变为null。 如果语句正常完成, Foo将保存对精确类型FooType的实例的引用,该实例之前在Universe中的任何位置都没有引用; 如果它抛出一个exception,它不会以任何方式影响Foo

构造函数的主要工作是初始化对象。 如果在初始化本身中存在exception,那么没有一个点没有正确初始化的对象。 因此,从构造函数抛出exception会导致null对象。