21

为什么在构造函数中抛出异常会导致空引用?例如,如果我们运行下面的代码,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;
  }

}
4

6 回答 6

40

构造函数永远不会完成,因此分配永远不会发生。不是从构造函数返回 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;
于 2012-04-12T08:25:28.337 回答
13

因为您正在检查引用

  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." );
}
于 2012-04-12T08:23:00.400 回答
3

当你在构造函数中抛出异常时,你破坏了对象的构造。所以它永远不会完成,因此,没有对象可以返回。事实上,赋值运算符 ( teacher = new Teacher( "", st );) 永远不会执行,因为异常会破坏调用堆栈。

并且 Teacher 构造函数仍然将对自身(正在构造的对象)的引用写入 Student 对象的属性。但是你不应该在之后尝试使用这个 Teacher 对象,因为它还没有被构造。它可能会导致未定义的行为。

于 2012-04-12T08:21:40.347 回答
1

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

在以下情况下:

  • 像上面这样的语句出现在try/catch块中
  • Foo无需事先编写就可以达到该声明。
  • Foocatch在块周围的上下文中定义的局部变量。
  • 从 catch 开始执行有可能到达一个语句,该语句Foocatch.

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

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

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

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

于 2015-06-03T16:48:19.347 回答
0

您在分配 'student.teacher = this; 之后抛出异常 //这一行执行 if ( name.Length < 5 ) //这被检查并且在指定的情况下为真 throw new ArgumentException( "Name must be at least 5 characters long." );//BAM : Exception throw here 。

所以teacher的值为null(在构造函数完成之前抛出异常),而st.teacher不是!

于 2012-04-12T08:36:50.763 回答
-1

构造函数的主要工作是初始化对象。如果初始化本身存在异常,则没有正确初始化的对象没有意义。因此,从构造函数中抛出异常会导致 null 对象。

于 2012-04-12T08:44:55.440 回答