Члены типов в C#
Класс или структура — это не просто набор полей и методов. Между «данными» и «поведением» в C# лежит целый слой членов, которые выглядят как одно, а работают как другое: свойство читается как поле, но за ним стоит пара методов; индексатор даёт синтаксис массива obj[i] у произвольного типа; const притворяется переменной, а на деле вшивается в код каждого вызывающего. Именно эти различия отделяют того, кто «пишет на C#», от того, кто понимает язык.
Эта тема — карта членов типа: чем свойство отличается от поля, почему const и readonly не синонимы, как перечисление становится битовым полем, откуда LINQ берёт методы у любой коллекции и почему StringBuilder спасает цикл конкатенации. Каждый слой ниже разбирает один член по механике, а не по названию.
Карта темы
- Свойства —
get/setкак пара методов, авто-свойство и скрытое backing-поле,initи валидация. - Индексаторы —
this[int i], свойство с параметрами, перегрузка по типу ключа. - const и readonly — время компиляции против времени выполнения, вшивание значения и версионирование сборок.
- Перечисления enum — именованные целочисленные константы, базовый тип,
[Flags]и степени двойки. - Методы-расширения —
static-метод с параметромthis, вызов как у экземпляра, корни LINQ. - Параметры ref и out — псевдоним вместо копии, кто и когда обязан присвоить,
inдля чтения. - Частичные классы — один тип в нескольких файлах, слияние при сборке, генераторы кода.
- StringBuilder — неизменяемость
string, ловушка O(n²) и растущий буфер.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
| Считать свойство бинарно совместимым с полем | Замена поля на свойство ломает уже скомпилированных вызывающих и сериализаторы — это разные члены |
| Думать, что у авто-свойства нет backing-поля | Компилятор молча генерирует скрытое поле; { get; set; } — не «просто поле» |
Ждать, что смена const в библиотеке дойдёт до вызывающих | Значение вшито в их скомпилированный код — нужна перекомпиляция всех потребителей |
Путать readonly со static | readonly по умолчанию экземплярное — у каждого объекта своё значение, в отличие от const |
Брать значения 1, 2, 3 для [Flags]-перечисления | Биты пересекаются, проверка через & даёт ложные срабатывания; нужны степени двойки |
| Ждать, что метод-расширение видит приватные члены | Он вызывается как static-метод и работает только через публичный API типа |
Считать ref и out копией переменной | Оба — псевдоним памяти вызывающего; вдобавок out метод обязан присвоить до возврата |
Собирать строку через + в цикле | Каждый + копирует всю строку заново — O(n²) и мусор для GC; нужен StringBuilder |
Значение для собеседований
Члены типов спрашивают не как список синтаксиса, а как проверку модели: понимаете ли вы, что стоит за знакомым { get; set; } или const. Кандидат, который говорит «свойство — это пара методов, поэтому замена поля на свойство не бинарно совместима», сразу отделяется от того, кто считает их одним и тем же.
Что обычно проверяют:
- Чем свойство отличается от публичного поля и почему у авто-свойства всё равно есть backing-поле.
- Разницу
constиreadonly— время компиляции против времени выполнения и последствие для версионирования сборок. - Что
[Flags]требует степеней двойки, а не последовательных значений. - Что метод-расширение — это
static-метод, который не имеет доступа к приватным членам. - Что
refиoutпередают псевдоним, и кто обязан инициализировать аргумент. - Почему
StringBuilderпревращает O(n²)-конкатенацию в O(n).
Типичный неверный ответ: «свойство и поле — это одно и то же, просто с get/set». Это открывает разговор о том, что свойство компилируется в методы, поэтому его реализацию можно менять без перекомпиляции вызывающих, а поле — нельзя.