Виды конструкторов си шарп
В прошлой статье для создания объекта использовался конструктор по умолчанию. Однако мы сами можем определить свои конструкторы. Как правило, конструктор выполняет инициализацию объекта. При этом если в классе определяются свои конструкторы, то он лишается конструктора по умолчанию.
На уровне кода конструктор представляет метод, который называется по имени класса, который может иметь параметры, но для него не надо определять возвращаемый тип. Например, определим в классе Person простейший конструктор:
Person tom = new Person(); // Создание объекта класса Person tom.Print(); // Имя: Tom Возраст: 37 class Person < public string name; public int age; public Person() < Console.WriteLine("Создание объекта Person"); name = "Tom"; age = 37; >public void Print() < Console.WriteLine($"Имя: Возраст: "); > >
Итак, здесь определен конструктор, который выводит на консоль некоторое сообщение и инициализирует поля класса.
Конструкторы могут иметь модификаторы, которые указываются перед именем конструктора. Так, в данном случае, чтобы конструктор был доступен вне класса Person, он определен с модификатором public .
Определив конструктор, мы можем вызвать его для создания объекта Person:
Person tom = new Person(); // Создание объекта Person
В данном случае выражение Person() как раз представляет вызов определенного в классе конструктора (это больше не автоматический конструктор по умолчанию, которого у класса теперь нет). Соответственно при его выполнении на консоли будет выводиться строка «Создание объекта Person»
Подобным образом мы можем определять и другие конструкторы в классе. Например, изменим класс Person следующим образом:
Person tom = new Person(); // вызов 1-ого конструктора без параметров Person bob = new Person("Bob"); //вызов 2-ого конструктора с одним параметром Person sam = new Person("Sam", 25); // вызов 3-его конструктора с двумя параметрами tom.Print(); // Имя: Неизвестно Возраст: 18 bob.Print(); // Имя: Bob Возраст: 18 sam.Print(); // Имя: Sam Возраст: 25 class Person < public string name; public int age; public Person() < name = "Неизвестно"; age = 18; >// 1 конструктор public Person(string n) < name = n; age = 18; >// 2 конструктор public Person(string n, int a) < name = n; age = a; >// 3 конструктор public void Print() < Console.WriteLine($"Имя: Возраст: "); > >
Теперь в классе определено три конструктора, каждый из которых принимает различное количество параметров и устанавливает значения полей класса. И мы можем вызвать один из этих конструкторов для создания объекта класса.
Консольный вывод данной программы:
Имя: Неизвестно Возраст: 18 Имя: Bob Возраст: 18 Имя: Sam Возраст: 25
Стоит отметить, что начиная с версии C# 9 мы можем сократить вызов конструктора, убрав из него название типа:
Person tom = new (); // аналогично new Person(); Person bob = new ("Bob"); // аналогично new Person("Bob"); Person sam = new ("Sam", 25); // аналогично new Person("Sam", 25);
Ключевое слово this
Ключевое слово this представляет ссылку на текущий экземпляр/объект класса. В каких ситуациях оно нам может пригодиться?
Person sam = new("Sam", 25); sam.Print(); // Имя: Sam Возраст: 25 class Person < public string name; public int age; public Person() < name = "Неизвестно"; age = 18; >public Person(string name) < this.name = name; age = 18; >public Person(string name, int age) < this.name = name; this.age = age; >public void Print() => Console.WriteLine($"Имя: Возраст: "); >
В примере выше во втором и третьем конструкторе параметры называются также, как и поля класса. И чтобы разграничить параметры и поля класса, к полям класса обращение идет через ключевое слово this . Так, в выражении
первая часть — this.name означает, что name — это поле текущего класса, а не название параметра name. Если бы у нас параметры и поля назывались по-разному, то использовать слово this было бы необязательно. Также через ключевое слово this можно обращаться к любому полю или методу.
Цепочка вызова конструкторов
В примере выше определены три конструктора. Все три конструктора выполняют однотипные действия — устанавливают значения полей name и age. Но этих повторяющихся действий могло быть больше. И мы можем не дублировать функциональность конструкторов, а просто обращаться из одного конструктора к другому также через ключевое слово this , передавая нужные значения для параметров:
class Person < public string name; public int age; public Person() : this("Неизвестно") // первый конструктор < >public Person(string name) : this(name, 18) // второй конструктор < >public Person(string name, int age) // третий конструктор < this.name = name; this.age = age; >public void Print() => Console.WriteLine($"Имя: Возраст: "); >
В данном случае первый конструктор вызывает второй, а второй конструктор вызывает третий. По количеству и типу параметров компилятор узнает, какой именно конструктор вызывается. Например, во втором конструкторе:
public Person(string name) : this(name, 18)
идет обращение к третьему конструктору, которому передаются два значения. Причем в начале будет выполняться именно третий конструктор, и только потом код второго конструктора.
Стоит отметить, что в примере выше фактически все конструкторы не определяют каких-то других действий, кроме как передают третьему конструктору некоторые значения. Поэтому в реальности в данном случае проще оставить один конструктор, определив для его параметров значения по умолчанию:
Person tom = new(); Person bob = new("Bob"); Person sam = new("Sam", 25); tom.Print(); // Имя: Неизвестно Возраст: 18 bob.Print(); // Имя: Bob Возраст: 18 sam.Print(); // Имя: Sam Возраст: 25 class Person < public string name; public int age; public Person(string name = "Неизвестно", int age = 18) < this.name = name; this.age = age; >public void Print() => Console.WriteLine($"Имя: Возраст: "); >
И если при вызове конструктора мы не передаем значение для какого-то параметра, то применяется значение по умолчанию.
Инициализаторы объектов
Для инициализации объектов классов можно применять инициализаторы . Инициализаторы представляют передачу в фигурных скобках значений доступным полям и свойствам объекта:
Person tom = new Person < name = "Tom", age = 31 >; // или так // Person tom = new() < name = "Tom", age = 31 >; tom.Print(); // Имя: Tom Возраст: 31
С помощью инициализатора объектов можно присваивать значения всем доступным полям и свойствам объекта в момент создания. При использовании инициализаторов следует учитывать следующие моменты:
- С помощью инициализатора мы можем установить значения только доступных из вне класса полей и свойств объекта. Например, в примере выше поля name и age имеют модификатор доступа public, поэтому они доступны из любой части программы.
- Инициализатор выполняется после конструктора, поэтому если и в конструкторе, и в инициализаторе устанавливаются значения одних и тех же полей и свойств, то значения, устанавливаемые в конструкторе, заменяются значениями из инициализатора.
Инициализаторы удобно применять, когда поле или свойство класса представляет другой класс:
Person tom = new Person < name = "Tom", company = < title = "Microsoft">>; tom.Print(); // Имя: Tom Компания: Microsoft class Person < public string name; public Company company; public Person() < name = "Undefined"; company = new Company(); >public void Print() => Console.WriteLine($»Имя: Компания: «); > class Company
Обратите внимание, как устанавливается поле company :
Деконструкторы
Деконструкторы (не путать с деструкторами) позволяют выполнить декомпозицию объекта на отдельные части.
Например, пусть у нас есть следующий класс Person:
class Person < string name; int age; public Person(string name, int age) < this.name = name; this.age = age; >public void Deconstruct(out string personName, out int personAge) < personName = name; personAge = age; >>
В этом случае мы могли бы выполнить декомпозицию объекта Person так:
Person person = new Person("Tom", 33); (string name, int age) = person; Console.WriteLine(name); // Tom Console.WriteLine(age); // 33
Значения переменным из деконструктора передаюся по позиции. То есть первое возвращаемое значение в виде параметра personName передается первой переменной — name, второе возващаемое значение — переменной age.
По сути деконструкторы это не более,чем более удобный способ разложения объекта на компоненты. Это все равно, что если бы мы написали:
Person person = new Person("Tom", 33); string name; int age; person.Deconstruct(out name, out age);
При получении значений из деконструктора нам необходимо предоставить столько переменных, сколько деконструктор возвращает значений. Однако бывает, что не все эти значения нужны. И вместо возвращаемых значений мы можм использовать прочерк _ . Например, нам надо получить только возраст пользователя:
Person person = new Person("Tom", 33); (_, int age) = person; Console.WriteLine(age); // 33
Поскольку первое возвращаемое значение — это имя пользователя, которое не нужно, в в данном случае вместо переменной прочерк.
Конструкторы экземпляров (руководство по программированию на C#)
Конструктор экземпляра объявляется для указания кода, выполняемого при создании нового экземпляра типа с помощью выражения new . Чтобы инициализировать статический класс или статические переменные в нестатическом классе, можно определить статический конструктор.
Как показано в следующем примере, можно объявить несколько конструкторов экземпляров в одном типе:
class Coords < public Coords() : this(0, 0) < >public Coords(int x, int y) < X = x; Y = y; >public int X < get; set; >public int Y < get; set; >public override string ToString() => $"(,)"; > class Example < static void Main() < var p1 = new Coords(); Console.WriteLine($"Coords #1 at "); // Output: Coords #1 at (0,0) var p2 = new Coords(5, 3); Console.WriteLine($"Coords #2 at "); // Output: Coords #2 at (5,3) > >
В предыдущем примере первый конструктор, без параметров, вызывает второй, и оба аргумента равны 0 . Для этого воспользуйтесь ключевым словом this .
При объявлении конструктора экземпляра в производном классе можно вызвать конструктор базового класса. Для этого используйте ключевое слово base , как показано в следующем примере:
abstract class Shape < public const double pi = Math.PI; protected double x, y; public Shape(double x, double y) < this.x = x; this.y = y; >public abstract double Area(); > class Circle : Shape < public Circle(double radius) : base(radius, 0) < >public override double Area() => pi * x * x; > class Cylinder : Circle < public Cylinder(double radius, double height) : base(radius) < y = height; >public override double Area() => (2 * base.Area()) + (2 * pi * x * y); > class Example < static void Main() < double radius = 2.5; double height = 3.0; var ring = new Circle(radius); Console.WriteLine($"Area of the circle = "); // Output: Area of the circle = 19.63 var tube = new Cylinder(radius, height); Console.WriteLine($"Area of the cylinder = "); // Output: Area of the cylinder = 86.39 > >
Конструкторы без параметров
Если у класса нет явных конструкторов экземпляров, C# предоставляет конструктор без параметров, который можно использовать для создания экземпляра этого класса, как показано в следующем примере:
public class Person < public int age; public string name = "unknown"; >class Example < static void Main() < var person = new Person(); Console.WriteLine($"Name: , Age: "); // Output: Name: unknown, Age: 0 > >
Этот конструктор инициализирует поля и свойства экземпляров в соответствии с необходимыми инициализаторами. Если поле или свойство не имеет инициализатора, его значение устанавливается в значение по умолчанию для типа поля или свойства. Если в классе объявлен хотя бы один конструктор экземпляра, C# не предоставляет конструктор без параметров.
Тип структуры всегда предоставляет конструктор без параметров, как показано ниже:
- В C# 9.0 и более ранних версий это неявный конструктор без параметров, который создает значение по умолчанию типа.
- В C# 10 и более поздних версий это либо неявный конструктор без параметров, который создает значение по умолчанию для типа, либо явно объявленный конструктор без параметров. Дополнительные сведения см. в разделе инициализация структуры и значения по умолчанию статьи Типы структур .
Основные конструкторы
Начиная с C# 12, можно объявить основной конструктор в классах и конструкциях. Все параметры помещаем в круглые скобки после имени типа:
public class NamedItem(string name) < public string Name =>name; >
Параметры основного конструктора находятся в область во всем тексте объявляющего типа. Они могут инициализировать свойства или поля. Их можно использовать в качестве переменных в методах или локальных функциях. Их можно передать в базовый конструктор.
Основной конструктор указывает, что эти параметры необходимы для любого экземпляра типа. Любой явно написанный конструктор должен использовать синтаксис инициализатора this(. ) для вызова основного конструктора. Это гарантирует, что параметры основного конструктора будут определенно назначены всеми конструкторами. Для любого class типа, включая record class типы, неявный конструктор без параметров не создается при наличии основного конструктора. Для любого struct типа, включая record struct типы, всегда создается неявный конструктор без параметров и всегда инициализирует все поля, включая параметры основного конструктора, в 0-битовом шаблоне. При написании явного конструктора без параметров он должен вызвать основной конструктор. В этом случае можно указать другое значение для параметров основного конструктора. В следующем коде показаны примеры основных конструкторов.
// name isn't captured in Widget. // width, height, and depth are captured as private fields public class Widget(string name, int width, int height, int depth) : NamedItem(name) < public Widget() : this("N/A", 1,1,1) <>// unnamed unit cube public int WidthInCM => width; public int HeightInCM => height; public int DepthInCM => depth; public int Volume => width * height * depth; >
В class типах и struct параметры основного конструктора доступны в любом месте текста типа. Их можно использовать в качестве полей-членов. При использовании параметра основного конструктора компилятор записывает параметр конструктора в закрытом поле с именем, созданным компилятором. Если основной параметр конструктора не используется в тексте типа, закрытое поле не записывается. Это правило предотвращает случайное выделение двух копий основного параметра конструктора, передаваемого в базовый конструктор.
Если тип включает модификатор record , компилятор вместо этого синтезирует открытое свойство с тем же именем, что и у основного параметра конструктора. Для record class типов, если параметр основного конструктора использует то же имя, что и базовый основной конструктор, это свойство является открытым свойством базового record class типа. Он не дублируется в производном record class типе. Эти свойства не создаются для типов, отличных record от типов.