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

时间:2012-04-12 08:19:18

标签: c# exception object constructor null

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

}

6 个答案:

答案 0 :(得分:37)

构造函数永远不会完成,因此永远不会发生赋值。它不是从构造函数返回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;

答案 1 :(得分: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." );
}

答案 2 :(得分:3)

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

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

答案 3 :(得分:1)

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

如果:

  • 上述声明发生在try / catch
  • 可以在未事先书写Foo的情况下发表声明。
  • Foocatch块周围的上下文中定义的局部变量。
  • 从catch开始执行可能会在Foo之后没有写入catch的情况下到达Foo的语句。

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

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

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

请注意,如果Foo = new FooType();有类似普通构造函数的内容,Foo将永远导致 Foo变为空,如果不是之前的话。如果语句正常完成,FooType将保存对精确类型Foo的实例的引用,该实例之前在Universe中的任何位置都没有引用;如果它抛出异常,它不会以任何方式影响private static final int TERMIN_LOADER_ID = 1; private static final int AUFGABEN_LOADER_ID = 2; @Override public Loader<Cursor> onCreateLoader(int i, Bundle bundle) { switch(i){ case TERMIN_LOADER_ID: return new TerminLoader(getActivity()); case AUFGABEN_LOADER_ID: throw new UnsupportedOperationException("AUFGABEN_LOADER_ID not implemented yet."); default: throw new UnsupportedOperationException("Unknown loader id."); } } @Override public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) { // mAdapter is a CursorAdapter //mAdapter.swapCursor(cursor); switch(cursorLoader.getId()){ case TERMIN_LOADER_ID: RadListView termineListView = (RadListView)getActivity().findViewById(R.id.termineRadListView); // now here we can fill our termineListView because we got the cursor ready ArrayList<Termin> termine = new ArrayList<>(); cursor.moveToFirst(); while(!cursor.isAfterLast()){ // loop over the results from the query and build our termine ArrayList // this is our MatrixCursor from our TerminLoader long terminId = cursor.getLong(cursor.getColumnIndex(CalendarContract.Events._ID)); String title = cursor.getString(cursor.getColumnIndex(CalendarContract.Events.TITLE)); long begin = cursor.getLong(cursor.getColumnIndex(CalendarContract.Events.DTSTART)); Termin termin = new Termin(terminId, title, begin); termine.add(termin); cursor.moveToNext(); } TerminListViewAdapter terminListViewAdapter = new TerminListViewAdapter(termine, getActivity()); termineListView.setAdapter(terminListViewAdapter); break; case AUFGABEN_LOADER_ID: throw new UnsupportedOperationException("AUFGABEN_LOADER_ID not implemented yet."); default: throw new UnsupportedOperationException("Unknown LOADER_ID."); } } @Override public void onLoaderReset(Loader<Cursor> cursorLoader) { switch(cursorLoader.getId()){ case TERMIN_LOADER_ID: // now here we can fill our termineListView because we got the cursor ready //RadListView termineListView = (RadListView)getActivity().findViewById(R.id.termineRadListView); ArrayList<Termin> termine = new ArrayList<>(); TerminListViewAdapter terminListViewAdapter = new TerminListViewAdapter(termine, getActivity()); this.termineListView.setAdapter(terminListViewAdapter); break; case AUFGABEN_LOADER_ID: throw new UnsupportedOperationException("AUFGABEN_LOADER_ID not implemented yet."); default: throw new UnsupportedOperationException("Unknown LOADER_ID."); } } @Override public void onStart() { super.onStart(); loadData(); } public void loadData(){ // reload termine getActivity().getSupportLoaderManager().initLoader(TERMIN_LOADER_ID, null, this); }

答案 4 :(得分:0)

您在分配后抛出异常 'student.teacher = this; //这一行被执行了       if(name.Length&lt; 5)//选中此项并在指定的情况下为true         抛出新的ArgumentException(“名称长度必须至少为5个字符。”); // BAM:此处抛出异常。

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

答案 5 :(得分:-1)

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