Какие модификаторы доступа ты знаешь в C#?

«Какие модификаторы доступа ты знаешь в C#?» — вопрос из категории ООП, который задают на 28% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В C# существует шесть модификаторов доступа, которые определяют область видимости и доступность типов (классов, структур, интерфейсов) и их членов (полей, свойств, методов).

Модификатор Доступность Область применения по умолчанию
public Не ограничена. Доступен из любого кода в той же сборке (assembly) или в других сборках, которые на нее ссылаются. Члены интерфейса, перечисления.
private Доступен только внутри объявляющего типа (класса или структуры). Члены класса и структуры.
protected Доступен внутри объявляющего типа и во всех производных (наследующих) классах. Производный класс может находиться в другой сборке. -
internal Доступен из любого кода в той же сборке, но не из других сборок. Типы (классы, структуры) без явного модификатора.
protected internal Объединение protected ИЛИ internal. Доступен: 1) из кода в той же сборке, 2) из производных классов (даже в других сборках). -
private protected (C# 7.2+) Пересечение protected И internal. Доступен только из производных классов, которые находятся в той же сборке. -

Практические примеры и сценарии использования:

// Файл: AssemblyA.dll
public class BaseClass
{
    public int PublicField;           // Виден всем.
    private int _privateField;        // Виден только внутри BaseClass.
    protected int ProtectedField;     // Виден в BaseClass и DerivedClass (даже в другом DLL).
    internal int InternalField;       // Виден во всей AssemblyA.dll.
    protected internal int ProtIntField; // Виден в AssemblyA.dll И в DerivedClass из любого DLL.
    private protected int PrivProtField; // Виден ТОЛЬКО в BaseClass и DerivedClass внутри AssemblyA.dll.
}

internal class InternalClass { } // Класс виден только внутри AssemblyA.dll.

// Файл: AssemblyB.dll (ссылается на AssemblyA.dll)
public class DerivedClass : BaseClass
{
    public void Demo()
    {
        PublicField = 1;      // OK
        // _privateField = 2; // ОШИБКА: недоступно
        ProtectedField = 3;   // OK (наследование)
        // InternalField = 4; // ОШИБКА: другая сборка
        ProtIntField = 5;     // OK (наследование работает)
        // PrivProtField = 6; // ОШИБКА: другая сборка (нарушено условие "private protected")
    }
}

// В другом классе AssemblyB.dll
class AnotherClass
{
    void Demo()
    {
        var obj = new BaseClass();
        obj.PublicField = 1;  // OK
        // obj.ProtectedField = 2; // ОШИБКА: нет наследования
        // obj.InternalField = 3;  // ОШИБКА: другая сборка
        // obj.ProtIntField = 4;   // ОШИБКА: другая сборка И нет наследования
    }
}

Рекомендации:

  • Соблюдайте принцип минимального доступа: начинайте с private и расширяйте видимость только при необходимости.
  • public интерфейс класса должен быть минимальным и стабильным.
  • Используйте internal для скрытия служебных типов и методов, которые не должны быть частью публичного API вашей библиотеки.
  • private protected полезен в больших проектах для создания "семейного" доступа внутри одной сборки.