【问题标题】: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;
  }

}

【问题讨论】:

    标签: c# exception object constructor null


    【解决方案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 即可访问该声明。
        • Foocatch 块周围的上下文中定义的局部变量。
        • 从 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 对象。

            【讨论】:

            • 这是不正确的;正如其他答案所指出的那样,它根本不会产生任何结果。
            猜你喜欢
            • 2013-03-25
            • 1970-01-01
            • 2013-04-06
            • 2016-08-31
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多