1. Введение
Давайте начнём с проблем, которые возникают при прямом использовании полей. Если вы объявите поле как public, его можно будет изменять сразу и напрямую из любой части программы:
public class Dog
{
public string Name;
}
Dog dog = new Dog();
dog.Name = ""; // O_o ... это как "имя собаки = пустая строка"!
Пользователь может присвоить полю Name совершенно невероятные значения, даже такие, которые не имеют смысла для вашей предметной области: пустую строку, слишком длинное имя, или вовсе null. Это как если бы кто-то собирал ваш новый шкаф ИКЕА, а вы дали ему полный доступ к заводу деревообработки.
Напоминаю, что инкапсуляция — это когда объект сам контролирует свои данные. Мы не доверяем внутренности каждому встречному-поперечному, а даём специальные "дверки" — свойства (Properties), через которые и происходит доступ к полю, но с возможностью проверки, логирования, модификации или других реакций на изменение значения.
2. Определение и синтаксис
Свойство — это особый член класса, который выглядит почти как поле, но на самом деле под капотом представляет собой пару специальных методов: getter (получить значение) и setter (установить значение). С помощью свойства мы можем:
- Разрешить (или запретить) чтение/запись данных;
- Добавить валидацию или логику при доступе к данным;
- Спрятать внутреннее поле и даже хранить значение где-то в другом месте.
Свойство объявляется очень похоже на поле, только с фигурными скобками и ключевыми словами get и set внутри.
[модификатор_доступа] тип ИмяСвойства
{
get { ... }
set { ... }
}
Вот пример для нашего класса Dog:
public class Dog
{
private string _name; // внутреннее поле (private!)
public string Name
{
get { return _name; } // "геттер": получить имя
set { _name = value; } // "сеттер": присвоить имя
}
}
Примечание: подчеркивание обычно используют для приватных полей (_name). Это стандарт стиля C#.
3. Механика работы свойства
Свойство — это своего рода «охранник», который стоит между внутренними данными объекта и внешним миром. Пример:
Dog dog = new Dog();
dog.Name = "Шарик";
Console.WriteLine(dog.Name);
Пояснение:
- Когда выполнение программы доходит до строки dog.Name = "Шарик"; — в этот момент происходит вызов set-метода свойства, и вы можете добавить туда любую нужную проверку (например, проверить, что имя не пустое).
- В момент Console.WriteLine(dog.Name); вызывается get-метод, который просто возвращает текущее значение или, если нужно, возвращает что-то динамически вычисленное.
Выглядит, как будто это обычное поле, но на самом деле здесь всё под контролем!
4. Почему свойства — это "Best Practice"
В большинстве случаев мы не даём к внутренним полям объекта прямой доступ. Даже если кажется, что проверки сейчас не нужны, привычка оборачивать данные в свойства здорово выручает, когда правила игры поменяются.
Пример "валидации" на этапе присваивания:
public class Dog
{
private string _name;
public string Name
{
get { return _name; }
set
{
if (string.IsNullOrWhiteSpace(value))
{
throw new ArgumentException("Имя собаки не может быть пустым!");
}
_name = value;
}
}
}
Теперь такая штука:
dog.Name = ""; // Выбросит исключение!
… защищает наш объект от абсурдных значений.
5. Свойства: только для чтения, только для записи и обычные
Иногда надо разрешать только просмотр значения (например, у собаки есть только год рождения, менять его нельзя), или наоборот — только установку (редко, но вдруг захотите устроить что-то загадочное).
- Только для чтения: пишем только get, убираем set.
- Только для записи: пишем только set, убираем get.
Примеры:
public class Dog
{
private int _birthYear = 2018;
// Только для чтения
public int BirthYear
{
get { return _birthYear; }
}
// Только для записи (очень редко встречается)
public string Secret
{
set { /* делаем что-то с value */ }
}
}
6. Свойства против полей
| Поле | Свойство | |
|---|---|---|
| Синтаксис | |
|
| Доступ | Прямой | Через get/set |
| Валидация | Нет | Можно добавить в set/get |
| Расширяемость | Нет | Можно модифицировать в любой момент |
| IDE-интеграция | Видны как поля | Видны как свойства (важно для фреймворков) |
Иллюстрация: Работа свойства
sequenceDiagram
participant User as Пользователь объекта
participant Dog as Объект Dog
participant Field as Приватное поле _name
User->>Dog: dog.Name = "Рыжик"
Dog->>Dog: set Name("Рыжик")
Dog->>Field: _name = "Рыжик"
User->>Dog: print(dog.Name)
Dog->>Dog: get Name()
Dog->>Field: читает _name
Dog->>User: возвращает "Рыжик"
7. Типичные ошибки и нюансы
Давайте разберёмся, где тут могут подстерегать ловушки.
Частая ошибка — перепутать поля и свойства, случайно открыть приватные данные наружу:
public string name; // Это поле! Его видно везде, опасно!
Лучше так:
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
Статические свойства бывают, чтобы хранить общие для всех объектов значения. Но используйте их с осторожностью.
Забавно: если у свойства только get и ни одного конструктора, то его невозможно изменить вообще — эта штука называется immutable property и активно используется в современных подходах к проектированию (мы подробно к этому вернёмся в лекциях про record и неизменяемость данных).
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ