Члены типов
Свойства, индексаторы, методы расширения, const и readonly, ref/out, перечисления и события.
9 вопросов
JuniorТеорияОчень частоЧто такое свойство в C#, и чем оно отличается от публичного поля?
Что такое свойство в C#, и чем оно отличается от публичного поля?
Свойство — это пара аксессоров get/set, дающая управляемый доступ к состоянию. Авто-свойство { get; set; } заставляет компилятор сгенерировать скрытое backing-поле. В отличие от публичного поля, свойство — это пара методов, так что можно добавить валидацию или логику в set, сделать его только для чтения или поменять реализацию позже, не ломая вызывающий код.
Типичные ошибки
- ✗Считать, что свойство компилируется в то же, что и поле, поэтому замена одного на другое бинарно совместима
- ✗Думать, что у авто-свойства нет backing-поля, тогда как компилятор молча генерирует его
- ✗Полагать, что свойство обязано открывать и
get, иset, а не может опустить setter
Уточняющие вопросы
- →Как реализовать свойство с собственной валидацией вместо авто-свойства?
- →Что меняет в доступе свойство с телом-выражением или
init-only?
JuniorТеорияЧастоВ чём разница между полями const и readonly в C#?
В чём разница между полями const и readonly в C#?
const — это константа времени компиляции: ей присваивается литерал при объявлении, она неявно static, а её значение вшивается прямо в скомпилированный код каждого вызывающего. Поле readonly устанавливается один раз во время выполнения — при объявлении или в конструкторе — может отличаться у каждого экземпляра и хранить нелитеральное значение. Смена const требует перекомпиляции всех потребителей; для readonly — нет.
Типичные ошибки
- ✗Полагать, что смена
constв библиотеке распространяется на вызывающих без их перекомпиляции - ✗Думать, что поле
readonlyнеявноstatic, какconst, а не экземплярное по умолчанию - ✗Пытаться присвоить
constнелитеральное значение или результат метода, что компилятор отвергает
Уточняющие вопросы
- →Почему у
readonly-поля ссылочного типа всё ещё можно изменить целевой объект? - →Когда вшивание
constв вызывающих вызывает баг версионирования между сборками?
MiddleТеорияЧастоВ чём разница между параметрами ref и out в C#?
В чём разница между параметрами ref и out в C#?
Оба передают псевдоним переменной вызывающего, а не копию, поэтому метод читает и пишет ту же память. Аргумент ref должен быть инициализирован до вызова, ведь метод может его прочитать. Аргумент out можно не инициализировать заранее, но метод ОБЯЗАН присвоить его до возврата. out используют для возврата дополнительных значений; ref — для двустороннего обмена.
Типичные ошибки
- ✗Думать, что
refилиoutпередаёт копию, поэтому изменения вызывающего теряются — они ссылаются на ту же память - ✗Забывать, что метод обязан определённо присвоить каждый параметр
outдо любого пути возврата - ✗Считать, что аргумент
outдолжен инициализировать вызывающий, тогда как этого требует толькоref
Уточняющие вопросы
- →Как объявление
out varсоздаёт переменную прямо в месте вызова? - →Почему
async-метод или итератор не может объявлять параметрыref/out?
JuniorТеорияИногдаЧто такое enum в C#, и что добавляет атрибут [Flags]?
Что такое enum в C#, и что добавляет атрибут [Flags]?
enum — это именованный набор целочисленных констант. Его базовый тип по умолчанию int, а первый член по умолчанию имеет значение 0, если не задать значения явно. [Flags] помечает перечисление как битовое поле, так что члены со значениями степеней двойки комбинируются побитовым | и проверяются через &, а ToString затем форматирует комбинации как список через запятую.
Типичные ошибки
- ✗Забывать, что первый член по умолчанию равен 0, поэтому отсутствие нулевого значения делает состояние по умолчанию непредставимым
- ✗Использовать последовательные значения 1, 2, 3 с
[Flags]вместо степеней двойки, отчего комбинации пересекаются - ✗Считать, что сам
[Flags]включает побитовую арифметику, тогда как любой enum уже поддерживает|и&
Уточняющие вопросы
- →Как сменить базовый тип перечисления на
byteилиlong, и зачем это нужно? - →Почему для
[Flags]-перечисления рекомендуют резервировать явный членNone = 0?
MiddleТеорияИногдаЧто такое метод расширения в C#, и как он объявляется?
Что такое метод расширения в C#, и как он объявляется?
Метод расширения — это static-метод в static-классе, чей первый параметр помечен this, например this T x. Компилятор позволяет вызывать его так, будто это метод экземпляра у T, поэтому можно добавлять методы к типу — включая интерфейс вроде IEnumerable<T> — не изменяя его и не наследуясь. Операторы LINQ — это методы расширения, находимые через using пространства имён.
Типичные ошибки
- ✗Думать, что метод расширения видит приватные члены расширяемого типа, как настоящий метод экземпляра
- ✗Считать, что метод расширения переопределяет одноимённый метод экземпляра — побеждает метод экземпляра
- ✗Забыть
usingпространства имён метода, отчего вызов не разрешается
Уточняющие вопросы
- →Что происходит, когда метод экземпляра и метод расширения имеют одинаковую сигнатуру?
- →Почему расширение
IEnumerable<T>делает метод доступным для любого типа коллекции?
MiddleТеорияИногдаЗачем использовать StringBuilder вместо конкатенации + в цикле?
Зачем использовать StringBuilder вместо конкатенации + в цикле?
Поскольку string неизменяем, каждый + в цикле не может изменить существующую строку — он выделяет совершенно новую строку и копирует все прежние символы, поэтому конкатенация n раз — это O(n²) работы и заваливает сборщик мусора одноразовыми строками. StringBuilder держит изменяемый растущий буфер и дописывает на месте, делая построение O(n). Вызовите ToString() один раз в конце для получения результата.
Типичные ошибки
- ✗Считать, что
+изменяет существующую строку, а не выделяет новую каждый раз - ✗Думать, что конкатенация в цикле — O(n), а не O(n²) из-за повторных полных копий
- ✗Применять
StringBuilderдля пары фиксированных конкатенаций, где обычный+проще и так же быстр
Уточняющие вопросы
- →Как задание начальной ёмкости
StringBuilderснижает число перевыделений? - →Когда компилятор оптимизирует несколько
+в один вызовString.Concat?
SeniorТеорияИногдаКак event ограничивает доступ по сравнению с обычным полем delegate?
Как event ограничивает доступ по сравнению с обычным полем delegate?
event оборачивает поле delegate, но открывает внешнему коду только += и -=, поэтому подписчики могут добавлять и снимать обработчики, но не могут вызвать делегат или перезаписать весь список. Только объявляющий тип может вызвать событие или переприсвоить его. Это инкапсулирует паттерн издатель/подписчик: мешает подписчику очистить других подписчиков или вызвать событие от имени издателя.
Типичные ошибки
- ✗Думать, что внешний код может вызывать или переприсваивать
event, как публичное поле delegate - ✗Путать, кому достаётся
+=/-=против вызова — подписчики добавляют/снимают, вызывает только владелец - ✗Считать, что
eventдобавляет потоки или асинхронность, а не только инкапсуляцию доступа
Уточняющие вопросы
- →Как пользовательские аксессоры
add/removeпозволяют управлять хранилищем подписок события? - →Почему вызов события через локальную копию делегата — потокобезопасный паттерн?
JuniorТеорияРедкоЧто делает ключевое слово partial для класса в C#?
Что делает ключевое слово partial для класса в C#?
partial позволяет разбить один class, struct, interface или метод на несколько файлов исходного кода, которые компилятор сливает в один тип при сборке. Каждая часть должна использовать partial и иметь то же имя и пространство имён. Главное применение — держать машинно-сгенерированный код — файлы дизайнера или вывод source-генератора — отдельно от написанного вручную, чтобы регенерация не затирала ваши правки.
Типичные ошибки
- ✗Думать, что
partialсливает по-разному названные классы, тогда как каждая часть должна иметь то же имя - ✗Считать, что части могут жить в разных пространствах имён, тогда как все должны совпадать
- ✗Полагать, что
partialпорождает несколько типов в рантайме, тогда как компилятор выдаёт ровно один
Уточняющие вопросы
- →Как ведёт себя
partial-метод, если ни одна часть не даёт его тело? - →Почему source-генераторы опираются на
partialдля безопасного расширения пользовательского класса?
MiddleТеорияРедкоЧто такое индексатор в C#, и как он связан со свойством?
Что такое индексатор в C#, и как он связан со свойством?
Индексатор позволяет обращаться к экземплярам типа через синтаксис скобок [], как obj[i], словно к массиву. Он объявляется ключевым словом this, например public T this[int i] { get; set; }. Индексатор по сути — это свойство с параметрами: у него есть аксессоры get/set, но нет имени, и его можно перегружать по разным типам параметров, например this[string key].
Типичные ошибки
- ✗Думать, что индексатор обязан оборачивать настоящий массив, тогда как тело
get/setможет вычислять или искать что угодно - ✗Считать, что у типа может быть лишь один индексатор, тогда как индексаторы перегружаются по типу параметра
- ✗Полагать, что индексатор только для чтения, тогда как он может объявить аксессор
set, как любое свойство
Уточняющие вопросы
- →Как добавить второй индексатор с ключом
stringрядом сint? - →В какое имя члена компилируется индексатор в сгенерированном IL?