web-dev-qa-db-ja.com

コンストラクターで例外をスローすると、参照がnullになるのはなぜですか?

コンストラクターで例外をスローすると、参照がnullになるのはなぜですか?たとえば、以下のコードを実行すると、teacherの値はnullになりますが、st.teacherはnullになりません(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;
  }

}
21
Setyo N

コンストラクターが完了することはないため、割り当ては発生しません。コンストラクターから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;
39
Jon Skeet

参照をチェックしているからです。

  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." );
}
13
Tigran

コンストラクターで例外をスローすると、オブジェクトの構造が壊れます。したがって、それは決して終了せず、したがって、返すオブジェクトはありません。実際、例外によって呼び出しスタックが中断されるため、その代入演算子(teacher = new Teacher( "", st );)は実行されません。

また、Teacherコンストラクターは、それ自体(構築中のオブジェクト)への参照をStudentオブジェクトのプロパティに書き込みます。ただし、このTeacherオブジェクトは作成されていないため、後で使用しないでください。未定義の動作が発生する可能性があります。

3

Fooが参照型の場合、ステートメントFoo = new FooType();はオブジェクトを作成し、コンストラクターの完了後、参照をFooに格納します。コンストラクターが例外をスローした場合、参照をFooに格納するコードは、Fooが書き込まれずにスキップされます。

次の場合:

  • 上記のようなステートメントは、try/catchブロック内で発生します
  • このステートメントには、Fooを事前に記述しなくても到達できます。
  • Foocatchブロックを囲むコンテキストで定義されたローカル変数。
  • キャッチから開始する実行は、Fooの後に書き込まずにcatchを読み取るステートメントに到達する可能性があります。

コンパイラーは、後者のFooの読み取りの試行は、Fooが書き込まれていなくても実行できると想定し、その場合はコンパイルを拒否します。ただし、コンパイラは、次の場合に、Fooを書き込まずに読み取ることを許可します。

  • Fooは、クラスフィールド、またはクラスフィールドに格納されている構造体のフィールド、クラスフィールドに格納されている構造体のフィールドに格納されている構造体のフィールドなどです。
  • Foooutパラメーターとして(C#以外の言語で記述された)メソッドに渡され、メソッドには何も格納されません。fooを読み取るステートメントは、メソッドが例外ではなく正常に返された場合にのみ到達可能です。

前者の場合、Fooの値はnullに定義されています。後者の場合、Fooの値は、メソッドの実行中に最初に作成されたときにnullになる可能性があります。ループ内で再作成された場合、nullまたは、最後に作成された後に書き込まれた最後の値が含まれる場合があります。この基準は、その状況で何が起こるかについて具体的ではありません。

FooTypeに通常のコンストラクターに似たものがある場合、Foo = new FooType();は決してcauseFooが以前にnullになることはありません。ステートメントが正常に完了すると、Fooは、以前はユニバースのどこにも参照が存在していなかった、正確なタイプFooTypeのインスタンスへの参照を保持します。例外がスローされても、Fooにはまったく影響しません。

1
supercat

割り当ての後に例外をスローしています 'student.teacher = this; //この行は次の場合に実行されますif(name.Length <5)//これはチェックされ、指定された場合はtrueですthrow new ArgumentException( "名前は少なくとも5文字の長さである必要があります。"); // BAM:ここで例外がスローされます。」

したがって、teacherの値はnullです(コンストラクターの完了前にスローされる例外として)が、st.teacherはnullではありません!

0
kingpin