Что такое инвариант класса
Что проверяют
Понимаете ли вы, ради чего нужна инкапсуляция. Инвариант это тот самый смысл, ради которого закрывают поля: не потому, что так принято, а потому, что иначе объект окажется в состоянии, которого в предметной области не бывает.
Как отвечать
Инвариант класса это условие на состояние объекта, истинное для любого экземпляра всегда, когда над ним не выполняется метод класса. Оговорка про границы важна: внутри метода инвариант разрешено временно нарушить, но к моменту возврата управления он обязан восстановиться.
Примеры звучат убедительнее определения: у периода начало не позже конца, у заказа список позиций не пуст, у счёта баланс равен сумме проводок, у дроби знаменатель не равен нулю.
Кто его удерживает: конструктор устанавливает инвариант и отказывается создавать объект на неподходящих аргументах; каждый публичный метод обязан оставить условие истинным; поля закрыты, публичных сеттеров нет, иначе проверки обходятся в одну строку.
public sealed class DateRange
{
public DateOnly From { get; }
public DateOnly To { get; }
public DateRange(DateOnly from, DateOnly to)
{
if (to < from) throw new ArgumentException("конец раньше начала", nameof(to));
From = from;
To = to;
}
}Где спотыкаются
Проверяют аргументы в методах и забывают про конструктор. Объект рождается сломанным, дальше методы честно работают с мусором, а расследование уходит далеко от места ошибки.
Обещают такую же гарантию на значимом типе. Структуру всегда можно получить через значение по умолчанию или как элемент созданного массива, минуя любой конструктор с проверками, поэтому условие вроде «не ноль» структура удержать не может. Для таких правил нужен класс.
Отдают наружу внутреннюю изменяемую коллекцию. Сумма позиций перестаёт сходиться с сохранённым итогом, хотя ни один метод класса никто не вызывал: правило удерживает только тот код, через который проходят все изменения.