为什么在构造函数中抛出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
情况下达成。 -
Foo
在catch
块周围的上下文中定义的局部变量。 - 从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对象。