为啥在构造函数中抛出异常会导致空引用?
Posted
技术标签:
【中文标题】为啥在构造函数中抛出异常会导致空引用?【英文标题】:Why throwing exception in constructor results in a null reference?为什么在构造函数中抛出异常会导致空引用? 【发布时间】:2012-04-24 13:57:57 【问题描述】:为什么在构造函数中抛出异常会导致空引用? 例如,如果我们运行下面的代码,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;
【问题讨论】:
【参考方案1】:构造函数永远不会完成,因此赋值永远不会发生。不是从构造函数返回 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;
【讨论】:
感谢您的精彩解释,我将问题中的“空对象”编辑为“空引用”。 很好的解释,你也只是证明了 C# 中未初始化的对象总是持有«null»。我开始怀疑这一点,因为当我尝试在 Visual Studio 中使用一个在特定条件下可能未初始化的对象时,无论如何检查了它是否为“null”,然后使用,编译器显示一个关于未初始化变量的错误。在我用«null»显式初始化对象后,错误消失了。谢谢你,现在我知道这只是 Visual Studio 的一个错误。 @Hi-Angel:不,这不是错误。这是 field 和局部变量之间的区别。一个字段有一个默认值,并且可以在没有设置的情况下使用 - 一个局部变量不能被读取,直到它被明确分配。【参考方案2】:因为您正在检查引用。
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." );
【讨论】:
【参考方案3】:当你在构造函数中抛出异常时,你破坏了对象的构造。所以它永远不会完成,因此,没有对象可以返回。事实上,赋值运算符 (teacher = new Teacher( "", st );
) 永远不会执行,因为异常会破坏调用堆栈。
Teacher 构造函数仍然会将对自身(正在构造的对象)的引用写入 Student 对象的属性。但是你不应该在之后尝试使用这个 Teacher 对象,因为它还没有被构造。这可能会导致未定义的行为。
【讨论】:
【参考方案4】:如果Foo
是引用类型,则语句Foo = new FooType();
将构造一个对象,然后,在构造函数完成后,将引用存储到Foo
。如果构造函数抛出异常,将跳过将引用存储到 Foo
的代码,而不会写入 Foo
。
在以下情况下:
类似上述的语句出现在try
/catch
块中
无需事先编写Foo
即可访问该声明。
Foo
在 catch
块周围的上下文中定义的局部变量。
从 catch 开始执行可能会到达一条读取为 Foo
的语句,而没有在 catch
之后写入。
编译器将假定后者读取Foo
的尝试可以在没有写入Foo
的情况下执行,并且在这种情况下将拒绝编译。编译器将允许 Foo
在没有被写入的情况下被读取,但是,如果:
Foo
是类字段,或者类字段中存储的结构字段,类字段中存储的结构字段中存储的结构字段等。
Foo
作为 out
参数传递给不存储任何内容的方法(用 C# 以外的语言编写),并且只有在方法正常返回时才能访问读取 foo
的语句而不是通过异常。
在前一种情况下,Foo
的定义值为null
。在后一种情况下,Foo
的值在方法执行期间第一次创建时可能为 null;如果在循环中重新创建,它可能包含null
或在上次创建后写入它的最后一个值;该标准并未具体说明在这种情况下会发生什么。
请注意,如果 FooType
有任何类似于普通构造函数的东西,Foo = new FooType();
永远不会导致 Foo
之前没有变为 null。如果语句正常完成,Foo
将持有对精确类型 FooType
的实例的引用,该引用以前在 Universe 中的任何地方都不存在;如果它抛出异常,它不会以任何方式影响Foo
。
【讨论】:
【参考方案5】:你在赋值后抛出异常 'student.teacher = this; //这一行被执行 if ( name.Length
所以teacher的值为null(在构造函数完成之前抛出异常),而st.teacher不是!
【讨论】:
【参考方案6】:构造函数的主要工作是初始化对象。如果初始化本身存在异常,则没有正确初始化的对象没有意义。 因此,从构造函数中抛出异常会导致 null 对象。
【讨论】:
这是不正确的;正如其他答案所指出的那样,它根本不会产生任何结果。以上是关于为啥在构造函数中抛出异常会导致空引用?的主要内容,如果未能解决你的问题,请参考以下文章