Кроме обычных методов в языке C# предусмотрены специальные методы доступа, которые называют свойства. Они обеспечивают простой доступ к полям классов и структур, узнать их значение или выполнить их установку.
Стандартное описание свойства имеет следующий синтаксис:
| 1 2 3 4 | [модификатор_доступа] возвращаемый_тип произвольное_название { // код свойства } |
Например:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class Person { private string name; public string Name { get { return name; } set { name = value; } } } |
Здесь у нас есть закрытое поле name и есть общедоступное свойство Name. Хотя они имеют практически одинаковое название за исключением регистра, но это не более чем стиль, названия у них могут быть произвольные и не обязательно должны совпадать.
Через это свойство мы можем управлять доступом к переменной name. Стандартное определение свойства содержит блоки get и set. В блоке get мы возвращаем значение поля, а в блоке set устанавливаем. Параметр value представляет передаваемое значение.
Мы можем использовать данное свойство следующим образом:
| 1 2 3 4 5 6 7 8 | Person p = new Person(); // Устанавливаем свойство - срабатывает блок Set // значение "Tom" и есть передаваемое в свойство value p.Name = "Tom"; // Получаем значение свойства и присваиваем его переменной - срабатывает блок Get string personName = p.Name; |
Возможно, может возникнуть вопрос, зачем нужны свойства, если мы можем в данной ситуации обходиться обычными полями класса? Но свойства позволяют вложить дополнительную логику, которая может быть необходима, например, при присвоении переменной класса какого-либо значения. Например, нам надо установить проверку по возрасту:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | class Person { private int age; public int Age { set { if (value { Console.WriteLine("Возраст должен быть больше 17"); } else { age = value; } } get { return age; } } } |
Если бы переменная age была бы публичной, то мы могли бы передать ей извне любое значение, в том числе отрицательное. Свойство же позволяет скрыть данные объекты и опосредовать к ним доступ.
Блоки set и get не обязательно одновременно должны присутствовать в свойстве. Если свойство определяют только блок get, то такое свойство доступно только для чтения - мы можем получить его значение, но не установить. И, наоборот, если свойство имеет только блок set, тогда это свойство доступно только для записи - можно только установить значение, но нельзя получить:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | class Person { private string name; // свойство только для чтения public string Name { get { return name; } } private int age; // свойство только для записи public int Age { set { age = value; } } } |
Хотя в примерах выше свойства определялись в классе, но точно также мы можем определять и использовать свойства в структурах.
Модификаторы доступа
Мы можем применять модификаторы доступа не только ко всему свойству, но и к отдельным блокам - либо get, либо set:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | class Person { private string name; public string Name { get { return name; } private set { name = value; } } public Person(string name) { Name = name; } } |
Теперь закрытый блок set мы сможем использовать только в данном классе - в его методах, свойствах, конструкторе, но никак не в другом классе:
| 1 2 3 4 5 6 | Person p = new Person("Tom"); // Ошибка - set объявлен с модификатором private //p.Name = "John"; Console.WriteLine(p.Name); |
При использовании модификаторов в свойствах следует учитывать ряд ограничений:
Модификатор для блока set или get можно установить, если свойство имеет оба блока (и set, и get)
Только один блок set или get может иметь модификатор доступа, но не оба сразу
Модификатор доступа блока set или get должен быть более ограничивающим, чем модификатор доступа свойства. Например, если свойство имеет модификатор public, то блок set/get может иметь только модификаторы protected internal, internal, protected, private
Автоматические свойства
Свойства управляют доступом к полям класса. Однако что, если у нас с десяток и более полей, то определять каждое поле и писать для него однотипное свойство было бы утомительно. Поэтому в фреймворк .NET были добавлены автоматические свойства. Они имеют сокращенное объявление:
| 1 2 3 4 5 6 7 8 9 10 11 | class Person { public string Name { get; set; } public int Age { get; set; } public Person(string name, int age) { Name = name; Age = age; } } |
На самом деле тут также создаются поля для свойств, только их создает не программист в коде, а компилятор автоматически генерирует при компиляции.
В чем преимущество автосвойств, если по сути они просто обращаются к автоматически создаваемой переменной, почему бы напрямую не обратиться к переменной без автосвойств? Дело в том, что в любой момент времени при необходимости мы можем развернуть автосвойство в обычное свойство, добавить в него какую-то определенную логику.
Стоит учитывать, что нельзя создать автоматическое свойство только для записи, как в случае со стандартными свойствами.
Автосвойствам можно присвоить значения по умолчанию (инициализация автосвойств):
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class Person { public string Name { get; set; } = "Tom"; public int Age { get; set; } = 23; } class Program { static void Main(string[] args) { Person person = new Person(); Console.WriteLine(person.Name); // Tom Console.WriteLine(person.Age); // 23 Console.Read(); } } |
И если мы не укажем для объекта Person значения свойств Name и Age, то будут действовать значения по умолчанию.
Стоит отметить, что в структурах мы не можем использовать инициализацию автосвойств.
Автосвойства также могут иметь модификаторы доступа:
| 1 2 3 4 5 6 7 8 | class Person { public string Name { private set; get;} public Person(string n) { Name = n; } } |
Мы можем убрать блок set и сделать автосвойство доступным только для чтения. В этом случае для хранения значения этого свойства для него неявно будет создаваться поле с модификатором readonly, поэтому следует учитывать, что подобные get-свойства можно установить либо из конструктора класса, как в примере выше, либо при инициализации свойства:
| 1 2 3 4 | class Person { public string Name { get;} = "Tom" } |
Сокращенная запись свойств
Как и методы, мы можем сокращать свойства. Например:
| 1 2 3 4 5 6 7 | class Person { private string name; // эквивалентно public string Name { get { return name; } } public string Name = name; } |