Модификаторы доступа
На двери дома стоит кодовый замок. Кнопки снаружи видит любой прохожий: стоит набрать шифр, и дверь откроется. А сам шифр хранится внутри замка, и с улицы его не прочитать и не поменять.
Пока все поля наших классов открыты, у дома такой защиты нет. Добавим в House шифр двери и метод, который её открывает:
var house = new House("1234");
Console.WriteLine(house.Code); // 1234
house.Code = "0000";
Console.WriteLine(house.Open("0000")); // True
class House
{
public string Code;
public House(string code) => Code = code;
public bool Open(string code) => code == Code;
}Давайте разберём, что здесь не так:
new House("1234")ставит на дверь шифр1234.- Любой код программы читает шифр через
house.Code, и эта строка печатает1234. house.Code = "0000";подменяет шифр, не зная старого.- Теперь
house.Open("0000")открывает дверь: замок есть, но толку от него нет.
Шифр двери должен быть виден только самому классу House. Для этого в C# есть модификаторы доступа.
Члены класса и модификаторы
Поля, методы и конструкторы, объявленные внутри класса, называют членами класса. Модификатор доступа — слово перед членом класса: оно решает, какой код может к нему обращаться. В этой теме нужны два модификатора.
public
public делает член открытым. К нему обращается любой код программы, в том числе инструкции верхнего уровня: house.Open("1234"). Открытыми делают то, чем пользуются снаружи объекта. Это кнопки замка.
Открытый член — обещание всему остальному коду. Если поле открыто, на него может опираться любая строка программы, и переименовать или убрать его потом трудно.
private
private делает член закрытым. К нему обращается только код самого класса: его методы и конструкторы. Закрытыми делают данные, которые класс бережёт сам. Это шифр внутри замка.
Закрытые поля по соглашению .NET называют с подчёркивания и маленькой буквы: _code. Перепишем дом:
var house = new House("1234");
Console.WriteLine(house.Open("0000")); // False
Console.WriteLine(house.Open("1234")); // True
class House
{
private string _code;
public House(string code) => _code = code;
public bool Open(string code) => code == _code;
}Конструктор и метод Open — код класса, поэтому им _code доступен. А строка house._code в инструкциях верхнего уровня уже не скомпилируется: ошибка CS0122. Узнать шифр снаружи нельзя, остаётся только набрать его на кнопках.
Здесь аналогия с замком неточна. Закрытое поле скрыто от кода за пределами класса, а не от других домов. Метод класса House может прочитать _code любого объекта House, а не только своего: граница проходит по классу.
Доступ по умолчанию
Член без модификатора закрыт: string _code; значит то же, что private string _code;. Поэтому в C# пишут public перед каждым членом, который должен быть виден снаружи. В коде самой платформы .NET private всё равно пишут явно, чтобы читатель не вспоминал правило.
Инкапсуляция
Закрытые поля и открытые методы вместе дают инкапсуляцию: класс сам хранит свои данные и сам решает, что с ними можно делать. Внешний код пользуется только открытыми членами и не может испортить данные в обход правил.
Если шифр двери понадобится менять, в класс добавляют открытый метод, например ChangeCode(oldCode, newCode), и он сначала сверяет старый шифр. Подменить шифр без проверки, как в первом примере, больше не выйдет.
Этот вопрос часто задают на собеседовании: «Что такое инкапсуляция и зачем она нужна?» Ответ из двух частей:
- данные объекта закрыты, а работают с ними через открытые методы класса;
- поэтому класс сам следит за правилами, а его устройство можно менять, не трогая код, который им пользуется.
Частая ошибка
Метод забыли открыть:
var house = new House("1234");
Console.WriteLine(house.Open("1234")); // ✗
class House
{
private string _code;
public House(string code) => _code = code;
bool Open(string code) => code == _code;
}error CS0122: 'House.Open(string)' is inaccessible due to its protection levelПрограмма не компилируется. У Open нет модификатора, значит, метод закрыт, и вызвать его снаружи класса нельзя. Сообщение говорит об этом прямо: метод недоступен из-за уровня защиты. Допишите public перед bool Open.
Главное
- Модификатор доступа решает, какой код может обращаться к члену класса:
public— любой,private— только код этого класса. - Член без модификатора закрыт, как если бы перед ним стояло
private. - Инкапсуляция: поля закрыты, а работа с ними идёт через открытые методы, поэтому класс сам следит за своими данными.
- Обращение к закрытому члену снаружи класса не компилируется:
CS0122.