Перейти к содержанию

Вопросы на собеседовании C# junior с примерами

Вопросы на собеседовании C# junior лучше готовить вместе с небольшими программами. Определение объясняет термин, а программа показывает границы его применения. В этом уроке разберём семь вопросов на примере учебного каталога и заметок о материалах. Это ориентир подготовки, а не обязательная программа интервью.

В локальном окружении подготовки этой статьи нет .NET SDK, поэтому примеры C# здесь не исполнялись. Подписи к выводу обозначают ожидаемый результат по описанному коду. В архиве есть цельный Program.cs: его можно поместить в новый консольный проект и выполнить там, где установлен подходящий SDK. Отдельный фрагмент с объявлением класса нужно размещать после кода верхнего уровня или оформлять внутри обычной структуры программы.

1. Что копируется при присваивании значения и ссылки?

Сравним число и экземпляр класса. Во втором случае обе переменные обращаются к одному объекту:

int originalCount = 1;
int copiedCount = originalCount;
copiedCount++;

var first = new Article { Opens = 1 };
var second = first;
second.Opens++;

Console.WriteLine(originalCount); // ожидается: 1
Console.WriteLine(first.Opens);   // ожидается: 2

class Article
{
    public int Opens { get; set; }
}

Изменение copiedCount не меняет первое число. Присваивание second = first копирует ссылку на экземпляр, поэтому изменение его свойства видно через обе переменные. Это различие важнее популярного ответа «одно лежит в стеке, другое в куче»: такое упрощение не объясняет все контексты хранения.

Если нужно создать независимый объект, требуется соответствующая операция создания или копирования его данных. Присваивание новой переменной само по себе этого не делает. Описание категорий доступно в документации Microsoft о типах значений и ссылочных типах.

2. Зачем nullable-значение, если можно записать ноль?

Предположим, рейтинг материала ещё неизвестен. Ноль может быть допустимой оценкой, поэтому он не подходит для обозначения отсутствия данных. Nullable-тип позволяет различить состояния:

int? rating = null;
Console.WriteLine(rating.HasValue); // ожидается: False
Console.WriteLine(rating ?? -1);    // ожидается: -1
rating = 0;
Console.WriteLine(rating.HasValue); // ожидается: True

Оператор ?? здесь задаёт запасное значение для вывода. Число -1 — лишь условная отметка упражнения; в интерфейсе обычно нужен понятный текст, например «не оценён». Не переносите техническую заглушку в предметные данные без определения её смысла.

Если спросили про nullable reference types, уточните другую задачу: аннотации и анализ компилятора помогают выявлять возможное использование отсутствующей ссылки. Это не тот же механизм, что Nullable<int>, и не автоматическая проверка всех входных данных во время выполнения. Полезный ответ сначала разделяет два случая, затем показывает пример каждого.

3. Чем List отличается от IEnumerable в вашем примере?

List<T> — конкретная коллекция с доступными операциями добавления и индексирования. IEnumerable<T> описывает возможность перечислить элементы. Получив второй тип, нельзя автоматически заключить, что данные уже материализованы или что повторный обход не выполнит новую работу.

var titles = new List<string> { "Grid", "LINQ" };
IEnumerable<string> sequence = titles;
titles.Add("HTTP");
Console.WriteLine(string.Join(", ", sequence));
// ожидается: Grid, LINQ, HTTP

В этой программе переменная sequence обращается к той же коллекции. Она не является снимком, созданным в момент присваивания. Если задаче нужен независимый список на определённый момент, его нужно сформировать явно, например вызовом ToList.

На интервью полезно спросить, о каком источнике идёт речь. Перечисление массива, ленивой последовательности и результата запроса к базе имеет разные практические последствия. Название интерфейса даёт общий контракт, но не раскрывает все свойства конкретной реализации.

4. Когда выполняется LINQ-запрос?

Многие операции LINQ возвращают последовательность с отложенным выполнением. На данных в памяти это можно увидеть без базы:

var counts = new List<int> { 1, 2 };
var query = counts.Where(value => value >= 2);
counts.Add(3);
Console.WriteLine(string.Join(", ", query)); // ожидается: 2, 3

Условие применяется при перечислении query, поэтому добавленная до обхода тройка попадает в результат. Если вызвать ToList сразу после Where, получится список результатов на тот момент, и последующее добавление в исходную коллекцию его не расширит.

Не все операции откладываются одинаково: вычисление количества или создание списка требует результата вызова. И LINQ к объектам нельзя без оговорок приравнивать к запросу через провайдер базы данных. Общие основы описаны в руководстве Microsoft по LINQ. На собеседовании назовите источник данных и момент, в который вам нужен результат.

5. Что делает async/await?

Асинхронный метод может вернуть задачу, завершения которой вызывающий код ожидает. Ключевые слова сами по себе не гарантируют отдельный поток или параллельную работу:

static async Task<int> GetCountAsync()
{
    await Task.Delay(10);
    return 3;
}

int count = await GetCountAsync();
Console.WriteLine(count); // ожидается: 3

Здесь задержка обозначает ожидаемую операцию упражнения. При незавершённой задаче метод приостанавливает продолжение, а вызывающая цепочка может ожидать итог. Для настоящего HTTP API вместо искусственной задержки будет конкретная операция ввода-вывода.

Не заменяйте ожидание блокирующим вызовом только ради привычного синхронного кода. И не утверждайте, что async ускоряет тяжёлый расчёт внутри метода. Для нагрузки на процессор нужно отдельно выбирать способ выполнения. Базовая модель изложена в документации Microsoft об асинхронном программировании.

Если следующий вопрос касается нескольких задач, разделите запуск и ожидание. Последовательно ожидать две операции и ожидать совместно уже начатые операции — разные маршруты. Нужно понимать их зависимость, обработку ошибок и ограничения ресурсов, прежде чем выбирать вариант.

6. Зачем ловить конкретное исключение?

Сбой чтения файла и неверный ввод пользователя требуют разных решений. Если поле должно содержать целое число, ожидаемую ошибку ввода удобно обработать через TryParse:

string input = "seven";
if (!int.TryParse(input, out int articleId))
{
    Console.WriteLine("Нужен целый идентификатор");
}

Этот фрагмент отвечает на вопрос формата строки. Допустимый диапазон идентификатора нужно проверить отдельно. Успешный разбор отрицательного числа ещё не означает корректность запроса к каталогу.

Если операция действительно бросает исключение, обработчик должен иметь смысл: восстановить состояние, сформировать понятный ответ или добавить контекст с сохранением причины. Пустой catch может скрыть сбой и оставить программу в состоянии, которое читатель кода считает успешным.

При повторном выбрасывании пойманного исключения важно различать throw; и throw error;: первый вариант сохраняет исходный путь исключения, как описано в документации Microsoft. На интервью объясните, какая информация нужна для диагностики и кто должен принять решение о результате операции, вместо общего «всё оборачиваю в try/catch».

7. Для чего using при работе с ресурсом?

Некоторые объекты требуют определённого завершения работы: например, закрытия открытого файла. using связывает область использования объекта с вызовом освобождения через соответствующий контракт:

using var reader = new StringReader("Grid\nLINQ");
Console.WriteLine(reader.ReadLine()); // ожидается: Grid

В учебном примере читается строка в памяти. Для реального файла смысл границы заметнее: ресурс должен быть освобождён после использования, в том числе при выходе из области из-за исключения. Этот контракт описан в справочнике Microsoft по using. Не объясняйте using как команду «немедленно удалить объект из памяти»: это смешивает освобождение ресурса и работу сборщика мусора.

Не каждое поле вашего класса нужно вручную освобождать. Сначала выясните, владеет ли объект соответствующим ресурсом и какой контракт он реализует. Для некоторых асинхронно освобождаемых ресурсов используется другой маршрут с await using; это отдельная тема, которую не стоит механически подставлять в любой пример.

Проверяем подготовку на собственном проекте

Возьмите учебный API и найдите по одному реальному месту для трёх вопросов: где может отсутствовать значение, где перечисляются данные, где ожидается операция. Если соответствующего кода нет, сделайте маленькое упражнение отдельно и обозначьте его как упражнение.

При наличии .NET SDK создайте консольный проект, замените его Program.cs файлом из архива и запустите dotnet run. Сопоставьте вывод с комментариями и измените одно условие. В этой статье результат такого запуска не заявлен; записать его нужно в своём окружении.

Затем объясните, какое поведение зависит от выбранного типа или момента выполнения. Для веб-реализации вернитесь к серии ASP.NET Core, а для карьерного пакета — к примеру резюме C#/.NET-разработчика.

Скачать Program.cs для самостоятельного запуска · Практикум по поиску работы