Страницы

Поиск по вопросам

четверг, 4 октября 2018 г.

Нужно ли вызывать базовый конструктор структур?

Троелсен пишет:
На заметку! Вы наверняка заметили в конструкторах Square и Rectangle связывание в цепочку со стандартным конструктором. Причина в том, что при наличии структуры, которая использует синтаксис автоматических свойств (как в данном случае), стандартный конструктор должен быть явно вызван (из всех специальных конструкторов) для инициализации закрытых поддерживающих полей. Да, это весьма своеобразное правило C#, но, в конце концов, ведь данная глава посвящена сложным темам.
И приводит в пример такой код:
public struct Rectangle { public int Width { get; set; } public int Height { get; set; } public Rectangle(int w, int h) : this() { Width = w; Height = h; } public void Draw() { for (int i = 0; i < Height; i++) { for (int j = 0; j < Width; j++) { Console.Write("*"); } Console.WriteLine(); } } public override string ToString() { return string.Format("[Width = {0}; Height = {1}]", Width, Height); } }
public struct Square { public int Length { get; set; } public Square(int i) : this() { Length = 1; } public void Draw() { for (int i = 0; i < Length; i++) { for (int j = 0; j < Length; j++) { Console.Write("*"); } Console.WriteLine(); } } public override string ToString() { return string.Format("[Length = {0}]", Length); } public static explicit operator Square(Rectangle r) { Square s = new Square(); s.Length = r.Height; return s; } }
Немного, не понимаю, зачем нужно вызывать базовый конструктор структур? Попробовал в дебаггере и так, и так вызвать и разницы не заметил...


Ответ

Кратко и по сути: раньше компилятор был глупее, сейчас это больше не обязательно.

Правило насчёт необходимости вызывать конструктор по умолчанию поменялось, начиная с версии C# 6.
Есть общее правило: все поля должны быть инициализированы. Оно неизменно. Когда вы вызываете конструктор без параметров, он инициализирует все поля значением по умолчанию (нулём или null, обычно).
Теперь смотрите, что происходит. Когда вы пишете
public struct Square { public int Length { get; set; } }
для свойства Length автоматически создаётся невидимое поле, в котором реально содержится значение свойства.
Старые версии языка вот в таком коде:
public Square(int i) { Length = 1; }
не могли распознать, что невидимое поле инициализируется до окончания конструктора, потому что вы обращаетесь лишь к свойствам. Ведь свойство — это не поле, и компилятор не знал, что именно произойдёт при вызове сеттера свойства! Поэтому было требование вызвать конструктор без параметров, который уж точно инициализировал всё как надо, чтобы компилятор был уверен на все сто.
Хуже того: поскольку компилятор не знал, что именно происходит в сеттере, он предполагал, что тот может захотеть читать поле, которое ещё не инициализировано! (Спасибо за замечание @PetSerAl.)
Текущая версия компилятора стала умнее, и распознаёт инициализацию автоматически созданных полей через вызов сеттера свойства. Поэтому сейчас вызов конструктора по умолчанию не нужен, если вы инициализируете все свойства в конструкторе. Замечание Троелсена устарело.

Обратите внимание, что вы реально должны инициализировать все поля перед использованием. Например, такой код не скомпилируется, и потребует вызова конструктора по умолчанию:
public struct Square { public int Length { get; set; } public int Age { get; set; } public Square(int i) { Length = 1; // Age не инициализировано } }
И такой:
public struct Square { public int Length { get; set; } public int Age { get; set; } public Square(int i) { Length = 1; Length += Age; // Age используется до инициализации Age = 10; } }

Кстати, компилятор стал умнее только насчёт автоматически определённых свойств. Если вы определяете свойство вручную, компилятор не пытается анализировать код. Например, вот такое не скомпилируется:
public struct Square { int length; public int Length { get { return length; } set { length = value; } } public Square(int i) { Length = 1; } }
потому что компилятор не может сообразить, что вы всё же инициализируете поле.

Комментариев нет:

Отправить комментарий