1. Автоматические свойства
В C# свойства (property) — это что-то среднее между полем и методом. По сути, это синтаксический сахар, который делает работу с инкапсуляцией максимально удобной. Вы обращаетесь к свойству так, будто это обычное поле, а внутри — может быть любая логика: проверка данных, изменение других полей, вызов методов и даже отправка e-mail вашей бабушке (последнее — не рекомендуется). В прошлой лекции мы писали свойства вручную. Но это может быстро приесться, если у тебя 100500 простых объектов, где нужны только "геттер" и "сеттер" — без особой логики.
Встречайте — автоматические свойства. Они избавят вас от шаблонного кода и позволят C# заботиться о хранении значения без вашего участия.
Как было раньше: ручные свойства
Когда мы хотим спрятать поле, но дать к нему доступ через свойство, обычно пишем вот так:
public class Dog
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
}
Подобный код — типичнейшее "занудство" (boilerplate). А ведь зачастую нам ничего "добавлять" в эти get/set не нужно! Просто дать доступ — и всё.
Как можно теперь
В C# всё стало проще: теперь не нужно вручную прописывать поля — компилятор берёт это на себя. Достаточно написать:
public class Dog
{
public string Name { get; set; }
}
И всё готово. Можно спокойно читать и менять значение Name, при этом под капотом компилятор сам создаёт приватное поле, которое снаружи никак не достать напрямую.
Что же на самом деле происходит за кулисами? Когда в коде появляется public string Name { get; set; }, компилятор автоматически создаёт скрытое поле вроде Name__BackingField. При каждом обращении к Name он вставляет нужный геттер или сеттер. Само поле напрямую в коде не видно, но оно существует — и с помощью рефлексии его даже можно найти (но это уже совсем другая история).
В чём кайф такого подхода? Во-первых, это гораздо короче и не требует повторять одно и то же. Во-вторых, код становится чище: меньше визуального шума, проще читать и сопровождать. И наконец, это безопаснее — никто не сможет напрямую ковыряться в полях, доступ идёт строго через свойства.
2. Синтаксис автоматических свойств
Синтаксис очень простой:
public class Dog
{
public string Name { get; set; }
public int Age { get; set; }
}
Теперь наш Dog выглядит гораздо лаконичнее! Мы можем создавать объекты и задавать значения привычно:
Dog myDog = new Dog();
myDog.Name = "Барбос";
myDog.Age = 4;
Схематично
| Способ | Синтаксис | Доступ к значению |
|---|---|---|
| Поле | |
Доступен напрямую |
| Ручное свойство | |
Доступ через get/set |
| Автосвойство | |
get/set + скрытое поле |
3. Кастомизация доступа: только чтение, только запись
Только для чтения: get-only
Если свойство нужно только читать (например, регистрационный номер питомца), можно сделать его доступным только для чтения — указав get, а set либо вообще не писать, либо сделать приватным. Такое значение можно присваивать только в конструкторе или прямо при объявлении:
public class Dog
{
public string RegistrationCode { get; }
public Dog()
{
RegistrationCode = "DOG-001"; // Присваиваем значение в конструкторе
}
}
Теперь извне можно только прочитать RegistrationCode, но не изменить. Отличный способ защитить важные данные от случайных изменений.
Ещё вариант: приватный set
public string Name { get; private set; }
Тогда свойство можно изменить только внутри класса (например, через метод или конструктор).
Только для записи: вредная затея
Чисто теоретически можно сделать set-only свойство (только для записи), но это встречается крайне редко и не рекомендуется — представьте объект, который ведёт себя как "чёрная дыра": туда можно что-то выкинуть, но достать обратно — никогда.
4. init-only сеттеры
Немного предыстории
Раньше, чтобы сделать неизменяемый объект (immutable), нужно было писать только get-свойства и задавать значения через конструктор. ОК, удобно — но не всегда. Но недавно в C# появился новый синтаксис: init-only свойства.
В чём суть init-сеттера?
- Позволяет установить значение свойства только при создании объекта (т.е. в конструкторе или в инициализаторе объекта).
- После создания объекта свойство становится только для чтения.
- Отлично подходит для "почти неизменяемых" классов, DTO, конфигураций, моделей.
public class Dog
{
public string Name { get; init; }
public int Age { get; init; }
}
Теперь мы можем сделать так:
Dog dog = new Dog { Name = "Бобик", Age = 2 };
dog.Name = "Рэкс"; // Ошибка! После создания - только чтение
В конструкторе тоже можно задавать значения:
public Dog(string name, int age)
{
Name = name;
Age = age;
}
Схема: когда можно присваивать
| Где присваивать? | { get; set; } | { get; private set; } | { get; init; } |
|---|---|---|---|
| Вне класса, после создания | ✅ | ❌ | ❌ |
| В конструкторе | ✅ | ✅ | ✅ |
| В инициализаторе | ✅ | ❌ | ✅ |
| Внутри класса | ✅ | ✅ | ❌ (кроме конструктора) |
5. Применение на практике
Давайте перепишем наш класс Dog, используя автоматические и init-only свойства:
public class Dog
{
// Свойство, устанавливается только при инициализации
public string Name { get; init; }
public int Age { get; init; }
// Автоматическое свойство для текущего состояния
public bool IsHungry { get; set; }
// Метод, действия собаки
public void Bark()
{
Console.WriteLine($"{Name} говорит: Гав!");
}
}
Теперь мы можем создавать Dog вот так:
Dog dog = new Dog { Name = "Шарик", Age = 3, IsHungry = true };
dog.Bark(); // выведет: Шарик говорит: Гав!
dog.IsHungry = false; // это свойство можно менять после создания
dog.Name = "Барбос"; // Ошибка! Свойство только для инициализации
6. Автоматические свойства с начальными значениями
В C# все сделано для вашего удобства. Можно даже сразу задать начальное значение свойству:
public class Dog
{
public string Name { get; set; } = "Безымянный";
public int Age { get; set; } = 0;
}
Или с init:
public class Dog
{
public string Name { get; init; } = "Щенок";
public int Age { get; init; } = 0;
}
Теперь если не указать Name при создании, оно будет "Щенок".
7. Автоматические свойства с разным уровнем доступа
Можно делать так, чтобы свойство читалось и писалось с разными модификаторами доступа:
public class Dog
{
public string Name { get; private set; }
public int Age { get; private set; }
public Dog(string name, int age)
{
Name = name;
Age = age;
}
}
В данном случае изменить имя и возраст можно только внутри класса Dog, например, в методах или конструкторе.
Сравнение ручных и автоматических свойств
| Подход | Сложность написания | Контроль в set/get | Изменяемость после создания | Использование в моделях |
|---|---|---|---|---|
| Ручное свойство | Длинно | Абсолютный | Любая | Там, где нужна логика |
| Автоматическое свойство | Коротко | Нет | Любая или приватная | Почти везде |
| init-only автоматическое | Коротко | Нет | Только при создании | DTO, конфиги |
8. Типичные ошибки при работе со свойствами
Ошибка №1: попытка присвоить значение get-only свойству вне конструктора.
Если свойство объявлено только с get, компилятор не позволит изменить его значение где-то ещё. Присваивать можно только в конструкторе или прямо при объявлении. Забыл — получишь ошибку компиляции.
Ошибка №2: путаница между полем и свойством.
Когда заменяют обычное поле на автоматическое свойство, важно убедиться, что в остальном коде нет прямого обращения к полю. Поле и свойство — разные сущности, и если оставить старые обращения, компилятор либо выдаст ошибку, либо программа будет вести себя непредсказуемо.
Ошибка №3: приватный set без понимания последствий.
Если у свойства есть приватный set, а в остальном коде всё ещё пытаются присваивать ему значения — будет ошибка. Часто это происходит случайно, особенно при копировании чужого кода. Всегда проверяй, доступен ли set там, где ты собираешься использовать его.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ