This lesson is not translated yet – the Russian original is below.
Публичные поля из прошлого урока удобны ровно до первой проверки. Свойства решают эту проблему, не меняя того, как класс выглядит снаружи.
Проблема публичного поля
public class Person
{
public int Age;
}
var person = new Person();
person.Age = -5; // никто не помешал
Отрицательный возраст класс принял молча. Проверку добавить некуда: присваивание полю – операция языка, в неё не вклиниться.
Классическое решение – закрыть поле и завести пару методов:
private int _age;
public int GetAge() => _age;
public void SetAge(int value)
{
if (value < 0)
{
throw new ArgumentException("Возраст не может быть отрицательным");
}
_age = value;
}
Работает, но читается тяжело: person.SetAge(person.GetAge() + 1) вместо person.Age++.
Свойство
Свойство выглядит снаружи как поле, а внутри является парой методов:
public class Person
{
private int _age;
public int Age
{
get => _age;
set
{
if (value < 0)
{
throw new ArgumentException("Возраст не может быть отрицательным");
}
_age = value;
}
}
}
Теперь снаружи всё выглядит просто:
person.Age = 19; // вызывается set, проверка срабатывает
Console.WriteLine(person.Age); // вызывается get
Блок get возвращает значение, блок set его принимает. Внутри set доступно ключевое слово
value – это то, что присваивают; объявлять его не нужно.
Поле _age, которое хранит данные, называют backing fieldПо-русски говорят «поле поддержки» или просто «поле за свойством», но в разговоре чаще звучит английский термин. – хранилищем за свойством.
Автосвойства
Если проверять нечего, писать хранилище руками незачем:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
Компилятор сам создаёт скрытое поле. Это самая частая форма записи в реальном коде.
Начинайте с автосвойства, а не с публичного поля. Когда проверка понадобится, вы просто раскроете get и set в полную форму – и ни одна строка вызывающего кода не изменится. Превращение поля в свойство таким свойством не обладает: для внешнего кода это разные вещи на уровне сборки.
Ограничение доступа
Доступ к чтению и записи настраивается по отдельности:
public class Person
{
public string Name { get; private set; } // менять может только сам класс
public int Id { get; } // только чтение, задаётся в конструкторе
public string Email { get; init; } // задаётся при создании, потом нельзя
}
init – это разрешение на присваивание только в момент создания объекта:
var person = new Person { Email = "a@b.ru" }; // можно
person.Email = "c@d.ru"; // ошибка компиляции
Вычисляемые свойства
Свойство не обязано хранить значение – оно может его считать:
public class Rectangle
{
public double Width { get; set; }
public double Height { get; set; }
public double Area => Width * Height;
}
Area не занимает памяти и всегда актуально: оно вычисляется при каждом обращении.
Свойство должно быть дешёвым. Читатель кода видит user.Orders и ждёт мгновенного ответа, а не запроса к базе данных. Если получение значения требует заметной работы, объявляйте метод – GetOrders() честно выглядит как действие.
Статические члены
Всё, что мы объявляли до сих пор, принадлежит объекту: у каждого экземпляра свои поля. Слово
static делает член принадлежностью типа.
public class Counter
{
public static int Total; // одно на всю программу
public int Value; // своё у каждого объекта
public Counter()
{
Total++;
}
}
Обращаются к нему через имя класса, а не через объект:
var a = new Counter();
var b = new Counter();
Console.WriteLine(Counter.Total); // 2
Сколько бы объектов ни создали, Total существует в одном экземпляре.
Статические методы
Метод, которому не нужны данные конкретного объекта, объявляют статическим:
public static class MathHelper
{
public static int Square(int n) => n * n;
}
Именно так устроен знакомый класс Math: Math.Sqrt(16) вызывается без создания объекта, потому
что квадратному корню не нужно состояние.
Класс целиком можно пометить static – тогда в нём разрешены только статические члены, и создать
его через new не выйдет. Это способ собрать вспомогательные функции в одном месте.
Статический метод не может обратиться к обычному полю: он не знает, о каком объекте речь. Ошибка «требуется ссылка на объект для нестатического поля» означает ровно это – вы вызвали из static-метода что-то, принадлежащее экземпляру.
Когда статика уместна
Хорошие поводы: вспомогательные функции без состояния (Math, string.Join), константы,
счётчики.
Плохой повод – сделать данные доступными отовсюду, чтобы не передавать их в параметрах. Статическое состояние – это глобальная переменная: её меняет кто угодно откуда угодно, а в многопоточной программе она вдобавок становится источником трудноуловимых ошибок.