Модификаторы доступа, static, sealed, partial

↑ 1.4 ООП в C♯ · ← Предыдущая · → Следующая

Обязательно~5 мин чтенияУровень: junior

Зачем это на собесе

Модификаторы задают границы инкапсуляции и наследования. Спросят, чем protected internal отличается от private protected, что такое static-класс, зачем sealed, для чего partial и как это связано с кодогенерацией.

Объяснение

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

МодификаторКто видит
publicвсе, в том числе другие сборки
internalтолько своя сборка (по умолчанию для типов верхнего уровня)
protectedсам класс и наследники (в любой сборке)
privateтолько сам тип (по умолчанию для членов класса)
protected internalнаследники или любой код своей сборки (объединение)
private protectedнаследники и только внутри своей сборки (пересечение, C# 7.2)
file (C# 11)только внутри одного файла (типы-хелперы, кодогенерация)
public class Patient
{
    private int _age;                  // по умолчанию для полей и методов
    protected virtual void Validate() { }
    internal string Snils { get; set; } = "";   // доступно в своей сборке (и через InternalsVisibleTo)
}
  • Для типов верхнего уровня допустимы только public и internal.
  • Наследник не может быть доступнее базового класса, метод не может возвращать менее доступный тип (иначе CS0050/CS0051).
  • InternalsVisibleTo открывает internal конкретной сборке — обычно проекту тестов.

static

  • Статический класс: нельзя создать экземпляр и наследовать; только статические члены. Для утилит и extension-методов.
  • Статический член принадлежит типу, а не экземпляру. Статические поля живут всё время работы приложения (по домену приложения).
  • Статический конструктор выполняется один раз до первого использования типа.
public static class DateExtensions
{
    public static bool IsWeekend(this DateOnly d) => d.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday;
}
  • static локальные функции и лямбды не захватывают внешние переменные — дешевле и без скрытых аллокаций.

sealed

Запрещает наследование класса или дальнейшее переопределение метода.

public sealed class ReportRenderer { }                 // от него нельзя наследоваться
public class Base { public virtual void Run() { } }
public class Mid : Base { public sealed override void Run() { } }   // дальше переопределять нельзя

Зачем: явно ограничить контракт расширения, безопасность, небольшая оптимизация (JIT может девиртуализировать вызовы). Хорошая практика: классы по умолчанию sealed, пока наследование не спроектировано.

partial

Разбивает определение типа (или метода) на несколько файлов. Все части компилируются как один тип.

// Patient.cs
public partial class Patient { public string Name { get; set; } = ""; }
// Patient.Validation.cs
public partial class Patient { public bool IsValid() => !string.IsNullOrWhiteSpace(Name); }

Где используется: сгенерированный код (Designer-файлы, gRPC/Protobuf, Source Generators, [GeneratedRegex], LoggerMessage) отделяется от рукописного. partial-методы (C# 9+) объявляются в одной части, а реализуются в другой.

readonly, const, abstract, virtual — рядом

  • readonly — поле присваивается только в конструкторе (см. тему про константы).
  • abstract — без реализации, только в абстрактном классе.
  • virtual — можно переопределить в наследнике.
  • override, new — переопределение и сокрытие (см. тему про наследование).

Нюансы и подводные камни

  • protected internal часто путают с private protected: первый расширяет доступ (ИЛИ), второй сужает (И).
  • Неявные модификаторы: без указания у члена класса — private, у типа верхнего уровня — internal, у члена интерфейса — public.
  • Публичные static изменяемые поля — глобальное состояние: гонки данных и трудность тестирования. Статическое состояние допустимо только для неизменяемых данных или потокобезопасных кэшей.
  • partial не «наследование»: это один тип. Модификаторы (public, sealed, базовые типы) должны согласовываться между частями.
  • Статические классы нельзя подменить в тесте: логику с побочными эффектами лучше выносить в сервис за интерфейсом.

Вопросы с ответами

Связанные темы