コンストラクターで例外をスローすると、参照が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;
}
}
コンストラクターが完了することはないため、割り当ては発生しません。コンストラクターから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;
参照をチェックしているからです。
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." );
}
コンストラクターで例外をスローすると、オブジェクトの構造が壊れます。したがって、それは決して終了せず、したがって、返すオブジェクトはありません。実際、例外によって呼び出しスタックが中断されるため、その代入演算子(teacher = new Teacher( "", st );
)は実行されません。
また、Teacherコンストラクターは、それ自体(構築中のオブジェクト)への参照をStudentオブジェクトのプロパティに書き込みます。ただし、このTeacherオブジェクトは作成されていないため、後で使用しないでください。未定義の動作が発生する可能性があります。
Foo
が参照型の場合、ステートメントFoo = new FooType();
はオブジェクトを作成し、コンストラクターの完了後、参照をFoo
に格納します。コンストラクターが例外をスローした場合、参照をFoo
に格納するコードは、Foo
が書き込まれずにスキップされます。
次の場合:
try
/catch
ブロック内で発生しますFoo
を事前に記述しなくても到達できます。Foo
catch
ブロックを囲むコンテキストで定義されたローカル変数。Foo
の後に書き込まずにcatch
を読み取るステートメントに到達する可能性があります。コンパイラーは、後者のFoo
の読み取りの試行は、Foo
が書き込まれていなくても実行できると想定し、その場合はコンパイルを拒否します。ただし、コンパイラは、次の場合に、Foo
を書き込まずに読み取ることを許可します。
Foo
は、クラスフィールド、またはクラスフィールドに格納されている構造体のフィールド、クラスフィールドに格納されている構造体のフィールドに格納されている構造体のフィールドなどです。Foo
はout
パラメーターとして(C#以外の言語で記述された)メソッドに渡され、メソッドには何も格納されません。foo
を読み取るステートメントは、メソッドが例外ではなく正常に返された場合にのみ到達可能です。前者の場合、Foo
の値はnull
に定義されています。後者の場合、Foo
の値は、メソッドの実行中に最初に作成されたときにnullになる可能性があります。ループ内で再作成された場合、null
または、最後に作成された後に書き込まれた最後の値が含まれる場合があります。この基準は、その状況で何が起こるかについて具体的ではありません。
FooType
に通常のコンストラクターに似たものがある場合、Foo = new FooType();
は決してcauseFoo
が以前にnullになることはありません。ステートメントが正常に完了すると、Foo
は、以前はユニバースのどこにも参照が存在していなかった、正確なタイプFooType
のインスタンスへの参照を保持します。例外がスローされても、Foo
にはまったく影響しません。
割り当ての後に例外をスローしています 'student.teacher = this; //この行は次の場合に実行されますif(name.Length <5)//これはチェックされ、指定された場合はtrueですthrow new ArgumentException( "名前は少なくとも5文字の長さである必要があります。"); // BAM:ここで例外がスローされます。」
したがって、teacherの値はnullです(コンストラクターの完了前にスローされる例外として)が、st.teacherはnullではありません!