Как вызвать конструктор c
В прошлой теме был разработан следующий класс:
И мы можем установить значения для переменных класса Person, можем получить их значения во внешние переменные. Однако если мы попробуем получить значения переменных name и age до их установки, то результаты будут неопределенными:
Чтобы избежать подобной ситуации применяются специальные функции инициализации или конструкторы. Они позволяют инициализировать объект класса. Так, изменим код программы следующим образом:
Теперь в классе Person определен конструктор:
По сути конструктор представляет функцию, которая может принимать параметры и которая должна называться по имени класса. В данном случае конструктор принимает два параметра и передает их значения полям name и age.
Если в классе определены конструкторы, то при создании объекта этого класса необходимо вызвать один из его конструкторов.
Вызов конструктора получает значения для параметров и возвращает объект класса:
После этого вызова у объекта person для поля name будет определено значение «Tom», а для поля age — значение 22. Вполедствии мы также сможем обращаться к этим полям и переустанавливать их значения.
Тажке можно использовать сокращенную форму инициализации:
По сути она будет эквивалетна предыдущей.
Консольный вывод определенной выше программы:
Подобным образом мы можем определить несколько конструкторов и затем их использовать:
В классе Person определено три конструктора, и в функции все эти конструкторы используются для создания объектов:
Хотя пример выше прекрасно работает, однако мы можем заметить, что все три конструктора выполняют фактически одни и те же действия — устанавливают значения переменных name и age. И в C++ можем сократить их определения, вызова из одного конструктора другой и тем самым уменьшить объем кода:
Запись Person(string n): Person(n, 18) представляет вызов конструктора, которому передается значение параметра n и число 18. То есть второй конструктор делегирует действия по инициализации переменных первому конструктору. При этом второй конструктор может дополнительно определять какие-то свои действия.
Таким образом, следующее создание объекта
будет использовать третий конструктор, который в свою очередь вызывает второй конструктор, а тот обращается к первому конструктору.
Инициализация констант и ссылок
В теле конструктора мы можем передать значения переменным класса. Однако константы и ссылки требуют особого отношения. Например, вначале определим следующий класс:
Этот класс не будет компилироваться, так как здесь есть две ошибки — отсутствие инициализации константы name и ссылки ageRef. Хотяя их значения устанавливаются в конструкторе, но к моменту, когда код инструкции из тела конструктора начнут выполняться, константы и ссылки уже должны быть инициализированы. И для этого необходимо использовать списки инициализации:
Списки инициализации представляют перечисления инициализаторов для каждой из переменных и констант через двоеточие после списка параметров конструктора:
Таким образом, все переменные, константы и ссылки получат значение, и никакой ошибки не возникнет.
Вызываем конструктор базового типа в произвольном месте
Недавно проходил собеседование, и среди прочих был вопрос о порядке вызова конструкторов в C#. После ответа собеседующий решил продемонстрировать эрудицию и заявил, что вот в Java конструктор базового типа можно вызвать в любом месте конструктора производного типа, и C#, конечно, в этом проигрывает.
Но это уже не имело значения, потому что вызов был принят.
Подготовка
Создаем цепочку наследования. Для простоты будем использовать конструкторы без параметров. В конструкторе будем выводить информацию о типе и идентификатор объекта, на котором он вызывается.
И получаем вывод:
Type ‘A’ .ctor called on object #58225482
Type ‘B’ .ctor called on object #58225482
Type ‘C’ .ctor called on object #58225482
Перед выполнением конструктор может вызвать либо другой конструктор того же типа, либо любой доступный конструктор базового типа. Если вызов не указан явно, компилятор подставит вызов конструктора базового типа без параметров. Если базовый тип не предоставляет такой конструктор, происходит ошибка компиляции. При этом конструктор не может явно вызвать сам себя:
и таким фокусом компилятор тоже не провести:
Удаление дублирующегося кода
Добавляем вспомогательный класс:
И заменяем во всех конструкторах
Однако теперь программа выводит:
Type ‘C’ .ctor called on object #58225482
Type ‘C’ .ctor called on object #58225482
Type ‘C’ .ctor called on object #58225482
В нашем случае можно использовать следующую хитрость. Кто знает о типах времени компиляции? Компилятор. А еще он выбирает перегрузки методов на основе этих типов. И для обобщенных типов и методов генерирует сконструированные сущности тоже он. Поэтому возвращаем правильный вывод типов, переписав метод Trace следующим образом:
Получение доступа к конструктору базового типа
Здесь на помощь приходит рефлексия. Добавляем в Extensions метод:
В типы B и C добавляем свойство:
Вызов конструктора базового типа в произвольном месте
Меняем содержимое конструкторов B и C на:
Теперь вывод выглядит так:
Type ‘A’ .ctor called on object #58225482
Type ‘B’ .ctor called on object #58225482
Type ‘A’ .ctor called on object #58225482
Type ‘C’ .ctor called on object #58225482
Type ‘A’ .ctor called on object #58225482
Type ‘B’ .ctor called on object #58225482
Type ‘A’ .ctor called on object #58225482
Изменение порядка вызова конструкторов базового типа
Внутри типа A создаем вспомогательный тип:
Так как здесь важна только семантика, конструктор типа целесообразно сделать закрытым. Создание экземпляров не имеет смысла. Тип предназначен исключительно для различения перегрузок конструкторов типа A и производных от него. По этой же причине тип следует разместить внутри A и сделать защищенным.
Добавляем в A, B и C соответствующие конструкторы:
Для типов B и C ко всем конструкторам добавляем вызов:
И вывод становится:
Type ‘C’ .ctor called on object #58225482
Type ‘B’ .ctor called on object #58225482
Type ‘A’ .ctor called on object #58225482
Осмысление результата
Добавив в Extensions метод:
и вызвав его во всех конструкторах, принимающих CtorHelper, мы получим вывод:
Type ‘A’ surrogate .ctor called on object #58225482
Type ‘B’ surrogate .ctor called on object #58225482
Type ‘C’ .ctor called on object #58225482
Type ‘A’ surrogate .ctor called on object #58225482
Type ‘B’ .ctor called on object #58225482
Type ‘A’ .ctor called on object #58225482
Порядок следования конструкторов по принципу базовый/производный, конечно, не изменился. Но все же порядок следования доступных клиентскому коду конструкторов, несущих смысловую нагрузку, удалось поменять благодаря перенаправлению через вызовы недоступных клиенту ничего не делающих вспомогательных конструкторов.
Использование конструкторов (Руководство по программированию на C#)
При создании экземпляра класса или структуры вызывается его конструктор. Конструкторы имеют имя, совпадающее с именем класса или структуры, и обычно инициализируют члены данных нового объекта.
В следующем примере класс с именем Taxi определяется с помощью простого конструктора. Затем оператор new создает экземпляр этого класса. Конструктор Taxi вызывается оператором new сразу после того, как новому объекту будет выделена память.
Конструктор, который не принимает никаких параметров, называется конструктором без параметров. Конструкторы без параметров вызываются всякий раз, когда создается экземпляр объекта с помощью оператора new , а аргументы в new не передаются. Дополнительные сведения см. в разделе Конструкторы экземпляров.
Если класс не является статическим, компилятор C# выделяет классам без конструкторов открытый конструктор без параметров, позволяющий создавать экземпляры классов. Дополнительные сведения см. в статье Статические классы и члены статических классов.
Создание экземпляров класса можно запретить, сделав конструктор закрытым, следующим образом:
Дополнительные сведения см. в разделе Закрытые конструкторы.
Конструкторы для типов структур похожи на конструкторы классов, но structs не могут содержать явный конструктор без параметров, так как он предоставляется компилятором автоматически. Этот конструктор инициализирует каждое поле в struct со значением по умолчанию. При этом конструктор без параметров вызывается только в том случае, если экземпляр struct создается с помощью переменной new . Например, этот код использует конструктор без параметров, Int32чтобы гарантировать, что целое число инициализируется:
Начиная с C# 10 тип структуры может содержать явный конструктор без параметров. Дополнительные сведения см. в разделе «Инициализация структуры и значения по умолчанию » статьи » Типы структуры «.
Однако следующий код вызывает ошибку компилятора, так как он не используется new , и потому что он пытается использовать объект, который не был инициализирован:
Кроме того, объекты на основе structs (включая все встроенные числовые типы) можно инициализировать или назначить, а затем использовать, как в следующем примере:
Поэтому вызов конструктора без параметров для типа значения не требуется.
Оба класса и structs могут определять конструкторы, принимающие параметры. Конструкторы, принимающие параметры, необходимо вызывать с помощью оператора new или base. Классы и structs могут определять также несколько конструкторов; для определения конструктора без параметров ни один их них не требуется. Пример:
Этот класс можно создать, воспользовавшись одним из следующих операторов:
Конструктор может использовать ключевое слово base для вызова конструктора базового класса. Пример:
В этом примере конструктор базового класса вызывается перед выполнением соответствующего ему блока. Ключевое слово base можно использовать как с параметрами, так и без них. Любые параметры для конструктора можно использовать как параметры для base или как часть выражения. Дополнительные сведения см. в разделе base.
В производном классе, если конструктор базового класса не вызывается явным образом с помощью base ключевого слова, конструктор без параметров, если он есть, вызывается неявно. Это означает, что следующие объявления конструкторов действуют одинаково:
Если базовый класс не предлагает конструктор без параметров, производный класс должен выполнить явный вызов базового конструктора с помощью base .
Конструктор может вызывать другой конструктор в том же объекте с помощью ключевого слова this. Как и base , this можно использовать с параметрами или без, а все параметры в конструкторе доступны как параметры this или как часть выражения. Например, второй конструктор в предыдущем примере можно переписать, используя this :
Применение ключевого слова this в приведенном выше примере привело к вызову конструктора:
Конструкторы могут иметь пометку public, private, protected, internal, protected internal или private protected. Эти модификаторы доступа определяют, каким образом пользователи класса смогут создавать класс. Дополнительные сведения см. в статье Модификаторы доступа.
Конструктор можно объявить статическим, используя ключевое слово static. Статические конструкторы вызываются автоматически непосредственно перед доступом к статическим полям и обычно используются для инициализации членов статического класса. Дополнительные сведения см. в разделе Статические конструкторы.
Спецификация языка C#
Дополнительные сведения см. в разделах Конструкторы экземпляров и Статические конструкторы в Спецификации языка C#. Спецификация языка является предписывающим источником информации о синтаксисе и использовании языка C#.