Модификаторы доступа определяют, какой код может использовать тип или член. С их помощью класс отделяет часть, на которую опирается другой код (свою открытую поверхность), от части, которую он может свободно менять (своих внутренностей). В C# их шесть, плюс значения по умолчанию, которые вы получаете, если не пишете ни одного.
Шесть модификаторов
| Модификатор | Содержащий тип | Производный тип, та же сборка | Другой код, та же сборка | Производный тип, другая сборка | Другой код, другая сборка |
|---|---|---|---|---|---|
public | да | да | да | да | да |
protected internal | да | да | да | да | нет |
protected | да | да | нет | да | нет |
internal | да | да | да | нет | нет |
private protected | да | да | нет | нет | нет |
private | да | нет | нет | нет | нет |
Сборка это один скомпилированный проект: создаваемый им .dll или .exe. «Та же сборка» означает «тот же проект», поэтому internal работает как «видно моему коду, скрыто от пользователей моей библиотеки».
private и public: инкапсуляция
private это значение по умолчанию для членов и правильный выбор для большинства полей. Класс открывает то, что нужно вызывающим, через методы и свойства public, а эти методы поддерживают согласованность данных объекта.
Вывод:
False
True
Priya: 180
Поскольку balance закрыто, изменить его можно только через Deposit и TryWithdraw, и оба соблюдают правила. Никакой другой код программы не сможет создать отрицательный баланс, как бы он ни был написан. Вот что даёт инкапсуляция: правила живут в одном месте.
private действует на уровне типа, а не объекта. Метод Account может читать other.balance у другого экземпляра Account, и именно так обычно пишут Equals и методы сравнения.
protected: видно производным классам
Член protected скрыт от внешнего мира, но доступен классам, которые наследуются от объявляющего класса.
Вывод:
Sam: 4000.00
Rita: 6400.00
Есть одно правило, которое удивляет. Внутри Manager можно использовать baseSalary у this или у другого Manager, но не у произвольного Employee:
class Manager : Employee
{
public decimal Compare(Employee other)
{
return baseSalary - other.baseSalary;
// error CS1540: Cannot access protected member 'Employee.baseSalary' via a qualifier
// of type 'Employee'; the qualifier must be of type 'Manager' (or derived from it)
}
}
Причина: other может оказаться Contractor, который тоже наследуется от Employee, а protected даёт доступ к вашей собственной ветви иерархии, а не к соседним.
Защищённое поле навсегда связывает с собой каждый производный класс. Многие кодовые базы предпочитают свойство или метод protected либо держат поля закрытыми и дают производным классам только то, что им нужно.
internal: видно внутри сборки
Члены и типы internal может использовать любой код того же проекта и никакой код снаружи. Библиотека использует это для вспомогательных функций, на которые опираются её открытые классы:
// In the MyShop.Pricing library project
public class PriceCalculator
{
public decimal Total(Cart cart) => TaxRules.Apply(cart.Subtotal);
}
internal static class TaxRules // callers of the library cannot see this class
{
internal static decimal Apply(decimal amount) => amount * 1.2m;
}
Типы верхнего уровня являются internal, если модификатор не указан. Поэтому класс в библиотеке классов, который вы забыли пометить public, «пропадает» в проекте, который на неё ссылается.
Проекты модульных тестов это отдельные сборки, поэтому код internal им тоже не виден. Стандартное решение это атрибут в библиотеке:
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("MyShop.Pricing.Tests")]
protected internal и private protected
Эти два модификатора объединяют предыдущие противоположными способами, и их названия легко перепутать:
protected internalэто protected или internal: любой код в той же сборке плюс производные классы в других сборках. Это более широкий вариант.private protected(C# 7.2) это protected и internal: только производные классы, которые к тому же находятся в той же сборке. Это более узкий вариант, для базового класса, точки расширения которого должны оставаться внутри вашей библиотеки.
public class ReportBase
{
protected internal string Title; // same assembly, or subclasses anywhere
private protected int RowLimit = 500; // subclasses in this assembly only (C# 7.2)
}
Значения по умолчанию, когда ничего не написано
| Где | По умолчанию | Допустимо |
|---|---|---|
| Тип, объявленный в пространстве имён | internal | public, internal, file (C# 11) |
| Член класса | private | все шесть |
| Член структуры | private | public, internal, private (от структур нельзя наследоваться) |
| Вложенный тип | private | все шесть |
| Член интерфейса | public | явные модификаторы разрешены начиная с C# 8 |
| Член перечисления | public | никакие |
Явное указание стоит одного слова и снимает вопрос у следующего читателя, поэтому большинство руководств по стилю просят писать модификатор, даже когда он совпадает со значением по умолчанию.
Доступ на уровне методов доступа
Свойство или индексатор может дать одному из методов доступа более узкий модификатор, чем у самого свойства:
Вывод:
report.pdf: 100%
Модификатор метода доступа должен быть строже, чем у свойства, и иметь его может только один из двух методов доступа.
Несогласованная доступность
Открытый член не может предоставлять тип, который менее видим, чем он сам, потому что вызывающие получили бы то, что им не разрешено называть:
internal class Discount { }
public class Checkout
{
public Discount Current() => null;
// error CS0050: Inconsistent accessibility: return type 'Discount' is less accessible than method 'Checkout.Current()'
}
То же правило даёт CS0051 для типов параметров и CS0053 для типов свойств. Исправьте, сделав тип таким же видимым, как член, или член таким же скрытым, как тип.
Выбор модификатора
Начинайте с самого строгого модификатора, который работает, и расширяйте его, только когда он нужен вызывающему. Сделать что-то видимее позже легко и никогда никому не вредит; сделать что-то менее видимым ломает каждого вызывающего, который это использовал. На практике: поля private, члены, составляющие назначение класса, public, вспомогательные функции private, а типы только для библиотеки internal.
Часто задаваемые вопросы
Какие модификаторы доступа есть в C#?
Их шесть: public (любой код), private (только содержащий тип), protected (содержащий тип и производные от него типы), internal (любой код в той же сборке), protected internal (та же сборка или производные типы где угодно) и private protected (производные типы в той же сборке, C# 7.2). C# 11 добавил file для типов, видимых только в одном исходном файле.
Какой модификатор доступа по умолчанию в C#?
Члены класса или структуры (поля, методы, свойства, вложенные типы) по умолчанию private. Типы верхнего уровня (класс, объявленный прямо в пространстве имён) по умолчанию internal. Члены интерфейсов и перечислений открытые.
Что означает protected в C#?
Член protected виден внутри своего класса и внутри любого производного от него класса, но не другому коду. Производный класс может использовать его только через свой собственный тип: внутри Manager работает this.baseSalary, а обращение к baseSalary у какого-то другого объекта Employee это ошибка CS1540.
Что такое internal в C#?
internal делает тип или член видимым для всего кода в той же сборке (скомпилированном .dll или .exe того же проекта) и невидимым снаружи. Так библиотека держит вспомогательные классы вне своего открытого API. [assembly: InternalsVisibleTo("MyLib.Tests")] позволяет видеть их и тестовому проекту.
Как исправить ошибку «is inaccessible due to its protection level»?
Это ошибка CS0122: код использует член, который ему не разрешено видеть, чаще всего поле с модификатором private по умолчанию. Если внешний код должен его читать, откройте свойство или метод; не делайте просто поле открытым. Если это тип из другого проекта, он, вероятно, internal.