Почему бросание исключения в конструкторе приводит к нулевой ссылке?
Почему бросание исключения в конструкторе приводит к нулевой ссылке?
Например, если мы запускаем коды ниже значения учителя, это значение равно 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;
}
}
Ответы
Ответ 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;
Ответ 2
Потому что вы проверяете референции.
try
{
teacher = new Teacher( "", st ); //this line raises an exception
// so teacher REMAINS NULL.
// it 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 );
) никогда не выполняется, поскольку исключение разбивает вызывающий стек.
И конструктор Учителя все еще записывает ссылку на себя (объект, создаваемый) в свойство объекта "Студент". Но вы никогда не должны пытаться использовать этот объект Учителя впоследствии, так как он не был построен. Это может привести к поведению undefined.
Ответ 4
Вы бросаете исключение после назначения
'student.teacher = это;//Эта строка выполнена if (name.Length < 5)//Это отмечено и является истинным в указанном случае throw new ArgumentException ( "Имя должно быть не менее 5 символов".);//BAM: Исключение выбрано здесь. '
Таким образом, значение учителя равно null (как исключение, возникшее перед завершением конструктора), а st.teacher - нет!
Ответ 5
Если Foo
является ссылочным типом, оператор Foo = new FooType();
будет строить объект, а затем, после завершения конструктора, сохраните ссылку в Foo
. Если конструктор генерирует исключение, код, который сохранит ссылку в Foo
, будет пропущен без Foo
, который был записан.
В тех случаях, когда:
- Оператор, подобный приведенному выше, встречается в блоке
try
/catch
- Заявление может быть достигнуто без
Foo
, написанного заранее.
-
Foo
локальная переменная, определенная в контексте, окружающем блок catch
.
- Возможно, чтобы выполнение, начинающееся с catch, получило оператор, который читает
Foo
, не записав его после catch
.
Компилятор предположит, что последняя попытка прочитать Foo
может быть выполнена без Foo
, которая была написана, и в этом случае откажется от компиляции. Компилятор позволит читать Foo
без написания, если:
-
Foo
- это поле класса или поле структуры, хранящееся в поле класса, поле структуры, хранящееся в поле структуры, хранящейся в поле класса, и т.д.
-
Foo
передается в качестве параметра out
методу (написанному на языке, отличном от С#), который ничего не хранит в нем, а оператор, который читает Foo
, будет доступен только в том случае, если метод возвратился обычно, а не через исключение.
В первом случае Foo
будет иметь определенное значение null
. В последнем случае значение Foo
, скорее всего, будет null при первом его создании во время выполнения метода; если он воссоздан в цикле, он может содержать null
или последнее значение, которое будет записано в него после последнего его создания; стандарт не является конкретным о том, что произойдет в этой ситуации.
Обратите внимание, что если FooType
имеет что-то похожее на обычный конструктор, Foo = new FooType();
никогда не приведет к тому, что Foo
станет нулевым, если оно не было раньше. Если инструкция завершается нормально, Foo
будет содержать ссылку на экземпляр точного типа FooType
, к которому ранее ссылка не существовала где-либо в юниверсе; если он генерирует исключение, это никак не повлияет на Foo
.
Ответ 6
Основная задача конструктора - инициализировать объект. Если в инициализации есть исключение, тогда нет смысла иметь объект, который не был правильно инициализирован.
Следовательно, исключение исключения из конструктора приводит к нулевому объекту.