Содержание
На данный момент мы познакомились с таким типом данных в C# как класс, научились создавать объекты и использовать инициализаторы объектов. Сегодня мы познакомимся с таким важным понятием как свойство и рассмотрим основные модификаторы доступа к членам класса.
Введение
На данный момент у нас есть класс, описывающие некоторое здание прямоугольной формы:
internal class Building { public double width; public double length; public double height; public double GetVolume() => width * length * height; public Building(): this(10) { } public Building(double width): this(width, 20) { } public Building(double width, double length): this(width, length, 30) { } public Building(double width, double length, double height) { this.width = width; this.length = length; this.height = height; } }
У этого класса определено три поля (длина, ширина, высота), три конструктора и один метод, возвращающий объем здания. С точки зрения синтаксиса языка C# с этим классом никаких проблем нет — компилятор не видит ошибок, а программа, использующая этот класс, прекрасно работает. Однако у этого класса поля (читай — переменные) определены с модификатором public
, что считается плохим тоном в программировании. Все переменные класса обычно должны быть недоступны извне, а доступ к ним должен осуществляться через свойства. Но, прежде, чем мы перейдем к объявлению свойств, рассмотрим какие модификаторы доступа к членам класса могут использоваться в C#.
Модификаторы доступа
Всего в C# существует четыре ключевых слова для указания уровня доступа к классам и его членам: public
, private
, protected
и internal
. Из этих ключевых слов определяются шесть уровней доступа:
public
— доступ к типу или члену класса возможен из любого другого кода в том числе из извне.private
— доступ к типу или члену возможен только из кода в том же объектеclass
илиstruct
.protected
— доступ к типу или члену возможен только из кода в том же объектеclass
либо вclass
, производном от этогоclass
.internal
— доступ к типу или члену возможен из любого кода в той же сборке, но не из другой сборки.protected internal
— доступ к типу или члену возможен из любого кода в той сборке, где он был объявлен, или из производногоclass
в другой сборке.private protected
— доступ к типу или члену возможен только из его объявляющей сборки из кода в том жеclass
либо в типе, производном от этогоclass
. Этот уровень доступа можно использовать, начиная с версии C# 7.2.
Модификаторы доступа можно указывать явно, например, как в нашем классе:
public double width; public double length; public double height;
а можем не указывать, тогда к члену класса по умолчанию будет применен модификатор private
.
Классы (class
) и структуры (struct
), которые объявляются без модификатора, по умолчанию имеют доступ internal
. Также, все классы и структуры, определенные в пространствах имен и не являющиеся вложенными в другие классы, могут иметь только два модификатора доступа — public
или internal
. Примеры использования различных модификаторов доступа рассмотрим на примере нашего класса, но, прежде, чем мы это сделаем, стоит разобраться с тем, что такое свойство.
Свойства класса
Как было сказано выше — давать доступ к полям класса извне (объявлять их с модификатором доступа public
) — признак плохого программирования. И в прошлой части, касающейся работы с классами, модификатор public
был использован всего лишь с одной целью — чтобы максимально кратко объяснить суть работы инициализаторов объектов без углубления в тему свойств класса.
Почему не стоит давать прямой доступ к полям класса? Это можно, опять же продемонстрировать на таком простом примере как наш Building
: сейчас мы открыли доступ к полям width
, length
и height
извне. При этом, все три поля имеют тип double
. Таким образом, зная,что из себя представляет этот тип данных, мы можем без проблем сделать вот так:
Building building = new Building(); building.height = -100.5;
никакой ошибки нет — мы просто задали отрицательную высоту здания и получим в итоге отрицательный объем. Так вот, в том числе, чтобы избежать подобных ситуаций и могут применяться свойства. Свойство позволяет обеспечить контролируемый доступ к полям класса — обеспечить дополнительную логику. Например,мы можем сделать ограничения по максимальному и минимальному значению поля, можем запретить изменять какое-либо поле класса и так далее.
В общем случае, свойство определяется в классе следующим образом:
[модификатор_доступа] тип имя_свойства { get {возвращаемое_значение;} set {устанавливаемое_значение;} }
Здесь модификатор_доступа
— это один из шести модификаторов доступа, рассмотренных выше. В большинстве ситуаций используется модификатор public
. Далее идёт тип_данных
и имя
свойства. В фигурных скобках содержатся блоки get
— для чтения поля,которому соответствует свойство и set
— для записи поля. Например, создадим свойство, с помощью которого мы будем определять высоту здания (представлена только часть кода, демонстрирующая объявление свойства):
class Building { double height; public double Height { get { return height; } set { if (value <= 0) throw new Exception("Высота здания не может быть менее или равна 0 метров"); height = value; } } }
На что здесь стоит обратить внимание:
- поле
height
у нас теперь без модификатора доступа (по умолчанию оно сталоprivate
) - объявлено публичное свойство
Height
, причем в блокеset
(запись поля) реализована дополнительная логика — проверка того, что задается положительное значение высоты здания. Если будет задана отрицательная или нулевая высота, то будет сгенерировано исключение. Об исключениях мы будем говорить далее в одной из частей учебника.
height
и свойство Height
различаются только написанием первой буквы. В C# принято следующее правило: поля класса пишутся с маленькой буквы, а названия свойств и методов — с большой. В принципе, никто не запрещает назвать поля и свойства по-разному — код будет работать, но считается хорошим тоном именовать поля и свойства одинаковыми словами (желательно, существительными)Теперь, если попробовать запустить приложение и выполнить вот такое присваивание:
Building building = new Building(); building.Height = -100.5;
то программа выдаст ошибку:
Так же, мы можем объявлять свойства доступные только для чтения (у таких свойств будет отсутствовать блок set
) и только для записи (у таких свойств отсутствует блок get
). Второй вариант свойств (только для записи) встречается крайне редко, а вот свойства только для чтения — довольно часто. Например, мы можем создать свойство «Объем» следующим образом:
class Building { //метод private double GetVolume() { return width * length * height; } //Свойства public double Volume { get { return GetVolume(); } } }
здесь мы объявили свойство Volume
доступное только для чтения. При этом, методу GetVolume()
мы установили модификатор private
, а свойству — public
.
Теперь, познакомившись с тем, что такое свойства в C# перепишем допишем наш класс и создадим необходимые свойства для задания параметров:
internal class Building { public double width; public double length; double height; public double Height { get => height; set { if (value <= 0) throw new Exception("Высота здания не может быть равна нулю или быть отрицательной"); height = value; } } public double Width { get => width; set { if (value <= 0) throw new Exception("Ширина здания не может быть равна нулю или быть отрицательной"); width = value; } } public double Length { get => length; set { if (value <= 0) throw new Exception("Высота здания не может быть равна нулю или быть отрицательной"); length = value; } } public double Volume { get => GetVolume(); } private double GetVolume() => width * length * height; public Building(): this(10) { } public Building(double width): this(width, 20) { } public Building(double width, double length): this(width, length, 30) { } public Building(double width, double length, double height) { this.width = width; this.length = length; this.height = height; } }
Чтобы обратиться к свойству мы должны написать имя объекта, нажать на клавиатуре точку и выбрать интересующее нас свойство:
Сокращенная форма записи свойств в C# (автоматические свойства)
Если в блоках get
и set
свойства не реализуется никакая дополнительная логика, то допускается сокращенная записи свойства. Например, добавим в наш объект свойство Name
(название здания) и применим сокращенную форму записи:
class Building { public string Name { get; set; } }
Как видите, в этом случае нам не требуется объявлять в классе дополнительное поле, а блоки get
и set
остаются пустыми. Такие свойства также называются автосвойствами или автоматическими свойствами. Автоматические свойства также, как и обычные используют поля для хранения данных, однако эти поля создаются не программистом, а компилятором.
Выражения для свойств в C#
Если реализация свойства представляет собой одиночное выражение, в качестве метода получения или задания можно использовать выражения для свойств. Например, у нас в классе у свойств Width
, Length
, Height
и Volume
в блоке get
используется одиночное выражение, более, того, в логическую операцию в блоке set
мы также можем «свернуть», используя тернарную операцию, поэтому мы можем упростить код нашего класса, используя выражения C#:
class Building { private double width; private double length; private double height; public Building() { width = 10; length = 10; height = 2; } //метод private double GetVolume() { return width * length * height; } //Свойства public double Height { get => height; set => height = value > 0 ? value : throw new Exception("Высота здания не может быть менее 0 метров"); } public double Width { get => width; set => width = value > 0 ? value : throw new Exception("Ширина здания не может быть менее 0 метров"); } public double Length { get => length; set => length = value > 0 ? value : throw new Exception("Длина здания не может быть менее 0 метров"); } public double Volume => GetVolume(); }
Использование выражений свойств, также, как и выражение switch
о котором мы говорили, когда рассматривали логические операции C#, или сокращенная запись методов, позволяют сделать код короче и интуитивно понятнее.
Значение свойства по умолчанию в C#
Иногда бывает необходимо инициализировать значение свойства значением, отличным, от значения по умолчанию для типа данных. Например, наше новое свойство Name
имеет тип данных string
(строка) и по умолчанию будет инициализировано как null
, так как строки относятся к ссылочным типам данных. Если мы хотим, чтобы сразу при создании объекта наше свойство получило некоторое значение по умолчанию, то мы можем сделать это следующим образом:
class Building { public string Name { get; set; } = "Неизвестное здание"; }
То есть сразу после закрывающей скобки }
для свойства ставится равно и пишется значение свойства по умолчанию.
Блок init
При создании свойств мы можем использовать ключевое слово init
вместо set
. Использование init
означает, что значение свойства можно определить только при создании (в конструкторе) или инициализации объекта. После того, как значение свойства определено изменить его невозможно. Например, вернемся к нашему классу и добавим для него новое автоматическое свойство:
internal class Building { public double width; public double length; double height; public string Id { get; init; } //здесь прочие свойства, методы и конструкторы класса }
Чтобы назначить этому свойству значение мы можем воспользоваться инициализатором:
Building building = new Building { Id = "aaaa-bbbb-3333"};
или конструктором, например, так:
public Building(double width, double length, double height) { Id = "indentificator"; this.width = width; this.length = length; this.height = height; }
Если мы попытаемся изменить значение свойства после создания и инициализации объекта, например, так:
Building building = new Building { Id = "aaaa-bbbb-3333"}; building.Id = "new ident";
то в Visual Studio мы сразу увидим ошибку:
Модификатор required
Модификатор required
доступен начиная с C# 11. Применение этого модификатора к свойству или полю класса означает, что оно должно быть инициализировано инициализатором объекта. Например, сделаем свойство Name
обязательным:
public required string Name { get; set; }
Теперь мы обязаны определить значение свойства в инициализаторе объекта. Если мы этого не сделаем, то получим ошибку:
Следующий код скомпилируется без ошибок:
using FirstClass; Building building = new Building { Id = "aaaa-bbbb-3333", Name = "Моё новое здание"};
Итого
Итак, сегодня мы познакомились в общих чертах с модификаторами и уровнями доступа к членам классов (полям, свойствам и методам), а также разобрались с тем, что представляют из себя свойства, как их объявлять в классе, делать свойства C# доступными только для чтения, а также использовать автосвойства и выражения для свойств C#. В следующий раз мы продолжим разбираться с классами и объектами C# и рассмотрим такой вопрос, как наследование.