Перейти к содержимому
Теория · Классы и объекты · урок 5 из 11

Модификаторы доступа

На двери дома стоит кодовый замок. Кнопки снаружи видит любой прохожий: стоит набрать шифр, и дверь откроется. А сам шифр хранится внутри замка, и с улицы его не прочитать и не поменять.

Пока все поля наших классов открыты, у дома такой защиты нет. Добавим в House шифр двери и метод, который её открывает:

C#
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;
}

Давайте разберём, что здесь не так:

  1. new House("1234") ставит на дверь шифр 1234.
  2. Любой код программы читает шифр через house.Code, и эта строка печатает 1234.
  3. house.Code = "0000"; подменяет шифр, не зная старого.
  4. Теперь house.Open("0000") открывает дверь: замок есть, но толку от него нет.

Шифр двери должен быть виден только самому классу House. Для этого в C# есть модификаторы доступа.

Члены класса и модификаторы

Поля, методы и конструкторы, объявленные внутри класса, называют членами класса. Модификатор доступа — слово перед членом класса: оно решает, какой код может к нему обращаться. В этой теме нужны два модификатора.

public

public делает член открытым. К нему обращается любой код программы, в том числе инструкции верхнего уровня: house.Open("1234"). Открытыми делают то, чем пользуются снаружи объекта. Это кнопки замка.

Открытый член — обещание всему остальному коду. Если поле открыто, на него может опираться любая строка программы, и переименовать или убрать его потом трудно.

private

private делает член закрытым. К нему обращается только код самого класса: его методы и конструкторы. Закрытыми делают данные, которые класс бережёт сам. Это шифр внутри замка.

Закрытые поля по соглашению .NET называют с подчёркивания и маленькой буквы: _code. Перепишем дом:

C#
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), и он сначала сверяет старый шифр. Подменить шифр без проверки, как в первом примере, больше не выйдет.

Этот вопрос часто задают на собеседовании: «Что такое инкапсуляция и зачем она нужна?» Ответ из двух частей:

  • данные объекта закрыты, а работают с ними через открытые методы класса;
  • поэтому класс сам следит за правилами, а его устройство можно менять, не трогая код, который им пользуется.

Частая ошибка

Метод забыли открыть:

C#
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;
}
TEXT
error CS0122: 'House.Open(string)' is inaccessible due to its protection level

Программа не компилируется. У Open нет модификатора, значит, метод закрыт, и вызвать его снаружи класса нельзя. Сообщение говорит об этом прямо: метод недоступен из-за уровня защиты. Допишите public перед bool Open.

Главное

  • Модификатор доступа решает, какой код может обращаться к члену класса: public — любой, private — только код этого класса.
  • Член без модификатора закрыт, как если бы перед ним стояло private.
  • Инкапсуляция: поля закрыты, а работа с ними идёт через открытые методы, поэтому класс сам следит за своими данными.
  • Обращение к закрытому члену снаружи класса не компилируется: CS0122.