Содержание
На данный момент мы познакомились с таким типом данных в C# как класс, научились создавать объекты и использовать инициализаторы объектов. Сегодня мы познакомимся с таким понятием как свойство и рассмотрим основные модификаторы доступа к членам класса.
Введение
На данный момент у нас есть класс, описывающие некоторое здание прямоугольной формы:
class Building { public double width; public double length; public double height; //Конструктор public Building(double width, double length, double height = 3) { this.width = width; this.length = length; this.height = height; } public Building(): this(20, 20, 3) { } //метод public double GetVolume() { return width * length * 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 метров"); else 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# перепишем допишем наш класс и создадим необходимые свойства для задания параметров:
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 { return height; } set { if (value < 0) throw new Exception("Высота здания не может быть менее 0 метров"); else height = value; } } public double Width { get { return width; } set { if (value < 0) throw new Exception("Ширина здания не может быть менее 0 метров"); else width = value; } } public double Length { get { return length; } set { if (value < 0) throw new Exception("Длина здания не может быть менее 0 метров"); else length = value; } } public double Volume { get { return GetVolume(); } } }
Чтобы обратиться к свойству мы должны написать имя объекта, нажать на клавиатуре точку и выбрать интересующее нас свойство:
Сокращенная форма записи свойств в 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; } = "Неизвестное здание"; }
То есть сразу после закрывающей скобки }
для свойства ставится равно и пишется значение свойства по умолчанию.
Итого
Итак, сегодня мы познакомились в общих чертах с модификаторами и уровнями доступа к членам классов (полям, свойствам и методам), а также разобрались с тем, что представляют из себя свойства, как их объявлять в классе, делать свойства C# доступными только для чтения, а также использовать автосвойства и выражения для свойств C#. В следующий раз мы продолжим разбираться с классами и объектами C# и рассмотрим такой вопрос, как наследование.