PrepBro
Профессии
PrepBro
Профессия:

Подготовка

  • Вопросы1513
  • Задачи21

Аналитика

  • hh статистика
  • Анализ резюме

Практика

  • Тестовое собеседование
  • Mock-собеседование
  • Менторы

Поддержка / отзывы

Telegram админа
Профессия:

Подготовка

  • Вопросы1513
  • Задачи21

Аналитика

  • hh статистика
  • Анализ резюме

Практика

  • Тестовое собеседование
  • Mock-собеседование
  • Менторы

Поддержка / отзывы

Telegram админа
Все 24 профессии
Android DeveloperData AnalystSystem Analyst1С DeveloperiOS DeveloperBusiness AnalystJava DeveloperData ScientistQA EngineerQA AutomationPHP BackendC/C++ BackendDevOps EngineerIT Project ManagerFrontend DeveloperNode.js BackendUnity DeveloperC# BackendProduct AnalystFlutter DeveloperPython DeveloperIT Product ManagerGo DeveloperData Engineer

© 2026 PrepBro. Все права защищены.

Telegram-бот

Задачи по C# Backend

Реализация потокобезопасного Singleton
2.2 Middle🔥 24💬 1

Решение

Анализ задачи

Singleton паттерн гарантирует, что класс имеет только один экземпляр и предоставляет глобальную точку доступа к нему. Ключевой вызов — обеспечить потокобезопасность при инициализации и ленивую загрузку.

Способ 1: Double-Checked Locking (КЛАССИЧЕСКИЙ, но опасный)

public class SingletonDoubleChecked
{
    private static SingletonDoubleChecked _instance;
    private static readonly object _lock = new object();

private SingletonDoubleChecked() { }

public static SingletonDoubleChecked GetInstance()
    {
        // Первая проверка (без блокировки) — быстрый путь
        if (_instance != null)
            return _instance;

// Если null, входим в блокировку
        lock (_lock)
        {
            // Вторая проверка (с блокировкой) — гарантирует инициализацию
            if (_instance == null)
            {
                _instance = new SingletonDoubleChecked();
            }
        }

return _instance;
    }
}
Читать полностью ->
Memory Management: Найти утечку памяти
2.0 Middle🔥 20💬 1

Утечка памяти в Event Subscription

Почему происходит утечка памяти?

Проблема в паттерне event subscription:

  1. Статический EventManager — существует всю жизнь приложения
  2. Подписка на событие — когда DataProcessor подписывается на DataReceived, EventManager сохраняет ссылку на экземпляр DataProcessor
  3. Отсутствие отписки — когда processor выходит из области видимости, он не удаляется из памяти, потому что EventManager всё ещё держит на него ссылку
  4. GC корни — статический EventManager является GC корнем, через который все подписанные обработчики остаются в памяти

В цикле из 10000 итераций все 10000 экземпляров DataProcessor остаются в памяти одновременно, хотя они больше не используются.

Решение 1: IDisposable паттерн

public class DataProcessor : IDisposable
{
    private bool _disposed = false;
Читать полностью ->
Refactoring: Устранение God Class
1.8 Middle🔥 19💬 1

Решение: Refactoring God Class - UserManager

Анализ проблемы

Класс UserManager нарушает принцип Single Responsibility Principle (SRP). Он отвечает за 10+ различных обязанностей:

  1. Data Access (DB операции)
  2. Password Validation
  3. Password Hashing
  4. Email отправка
  5. Authentication
  6. JWT Token генерация
  7. Logging активности
  8. Statistics вычисление

Это делает код хрупким, сложным для тестирования и модификации.

Нарушения SOLID принципов

Single Responsibility: НАРУШЕНИЕ ✗

  • Класс имеет более 10 причин для изменения
  • Password logic, Email logic, Auth logic, DB logic — 4 разные причины

Open/Closed: НАРУШЕНИЕ ✗

  • Чтобы добавить новый способ авторизации (OAuth, SAML) — меняем класс
  • Чтобы добавить новый способ отправки (SMS вместо Email) — меняем класс

Dependency Inversion: НАРУШЕНИЕ ✗

  • DbContext жёстко встроен в класс
  • Невозможно подменить реализацию на mock для тестов

Решение: Разбиение на специализированные сервисы

Читать полностью ->
LINQ: Получить топ-10 популярных книг
2.2 Middle🔥 19💬 1

Решение

Анализ задачи

LINQ (Language Integrated Query) — это мощный инструмент для работы с коллекциями данных. Ключевые моменты:

  • Отложенное выполнение — запросы выполняются только при обращении к результатам
  • Два синтаксиса — методы расширения и query expressions
  • Деferred execution — результаты вычисляются лениво

1. Топ-10 книг по количеству выдач

Синтаксис методов (Method Syntax):

var top10Books = books
    .OrderByDescending(b => b.BorrowCount)  // Сортировка по выдачам (убывающий порядок)
    .Take(10)                                  // Берём первые 10
    .ToList();                                 // Материализуем результат

Query Expression Syntax:

var top10Books = (
    from b in books
    orderby b.BorrowCount descending
    select b
)
.Take(10)
.ToList();
Читать полностью ->
Синхронизация потоков: чётные и нечётные числа
1.8 Middle🔥 19💬 1

Решение

Анализ задачи

Это классическая задача на синхронизацию потоков, где требуется обеспечить упорядоченный вывод чисел. Сложность заключается в необходимости строгого чередования — нечётное число должно вывести первый поток, затем обязательно второй поток выведет чётное число, и так далее.

Выбор механизма синхронизации

Для этой задачи наиболее подходит AutoResetEvent — легковесный примитив синхронизации, который автоматически переходит в несигнализированное состояние после выхода из WaitOne(). Это идеально для синхронизации двух потоков в чередующемся режиме.

Почему именно AutoResetEvent:

  • Минимальные накладные расходы (в отличие от Monitor)
  • Автоматический сброс сигнала после пробуждения потока
  • Нет необходимости в Mutex
  • Ясная семантика «один сигнал = пробуждение одного потока»

Реализация

using System;
using System.Threading;
Читать полностью ->
ASP.NET Core: Custom Middleware для логирования
2.0 Middle🔥 18💬 1

Решение

Анализ задачи

Middleware в ASP.NET Core — это компонент, который обрабатывает HTTP-запросы и ответы. Pipeline работает как матрёшка: каждое middleware может:

  1. Обработать входящий запрос
  2. Передать управление следующему middleware через _next(context)
  3. Обработать исходящий ответ

Ключевые моменты:

  • Middleware выполняется в порядке регистрации в Program.cs
  • Нужно правильно вызвать _next() для продолжения pipeline
  • Нужно исключить статические файлы из логирования
  • Нужно захватить время выполнения
  • Нужно обработать исключения

Базовая реализация

using System.Diagnostics;

public class RequestLoggingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<RequestLoggingMiddleware> _logger;

public RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }
Читать полностью ->
Generics: Реализация универсального Repository
2.2 Middle🔥 17💬 1

Generic Repository Pattern с Unit of Work

Базовые интерфейсы

// 1. Интерфейс для всех сущностей в системе
public interface IEntity
{
    int Id { get; set; }
}

// 2. Основной interface репозитория
public interface IRepository<T> where T : class, IEntity
{
    // CRUD операции
    Task<T?> GetByIdAsync(int id);
    Task<IEnumerable<T>> GetAllAsync();
    Task<T> AddAsync(T entity);
    Task UpdateAsync(T entity);
    Task DeleteAsync(int id);
    
    // Расширенные операции
    Task<IEnumerable<T>> GetAsync(Expression<Func<T, bool>> predicate);
    Task<T?> FirstOrDefaultAsync(Expression<Func<T, bool>> predicate);
    Task<int> CountAsync(Expression<Func<T, bool>>? predicate = null);
    Task<bool> ExistsAsync(Expression<Func<T, bool>> predicate);
}
Читать полностью ->
Оптимизация LINQ запроса с multiple enumerations
2.0 Middle🔥 16💬 1

Оптимизация LINQ: Multiple Enumeration

Почему код неэффективен?

Проблема: Multiple Enumeration (множественное перечисление)

Когда IEnumerable передаётся как параметр, каждый раз когда мы его перечисляем, выполняется повторная обработка всех данных:

public void ProcessOrders(IEnumerable<Order> orders) // Это может быть LINQ запрос!
{
    Console.WriteLine($"Total orders: {orders.Count()}");  // 1-й проход
    
    if (orders.Any())  // 2-й проход
    {
        var firstOrder = orders.First();  // 3-й проход (до первого элемента)
        Console.WriteLine($"First order: {firstOrder.Id}");
    }
    
    foreach (var order in orders)  // 4-й проход (полный)
    {
        ProcessOrder(order);
    }
    
    var totalAmount = orders.Sum(o => o.Amount);  // 5-й проход
    Console.WriteLine($"Total amount: {totalAmount}");
}

Примеры опасных сценариев:

Читать полностью ->
SQL: Оптимизация медленного запроса
2.0 Middle🔥 16💬 1

Решение

Анализ проблемы

Оригинальный запрос медленный потому что:

  1. Нет индекса на колонках WHERE (CustomerId, OrderDate, Status)
  2. **SELECT *** — загружаются ненужные колонки
  3. Нет covering index — требуется обращение к основной таблице
  4. Сложное условие — несколько WHERE условий без правильного индекса

1. Рекомендуемые индексы

-- ✅ Основной индекс: составной (composite index)
CREATE NONCLUSTERED INDEX IX_Orders_CustomerId_OrderDate_Status
  ON Orders (CustomerId, OrderDate DESC, Status)
  INCLUDE (TotalAmount);

-- Порядок колонок КРИТИЧЕН!
-- 1. CustomerId — фильтруется по точному значению (Seek)
-- 2. OrderDate DESC — фильтруется по диапазону и сортируется
-- 3. Status — дополнительный фильтр
-- INCLUDE (TotalAmount) — covering index (не нужна основная таблица)

Почему этот порядок?

Читать полностью ->
Исправление Race Condition в многопоточном коде
2.0 Middle🔥 15💬 1

Race Condition в многопоточном коде

Почему возникает race condition?

Проблема состояния в следующем:

  1. Проверка не атомарна — Код проверяет if (_balance >= amount), но в момент после проверки другой поток может снять деньги
  2. Несинхронизированный доступ — Два потока одновременно могут читать одно значение _balance
  3. Временное окно — Thread.Sleep(1) создаёт большое окно для race condition

Пример сценария:

Поток 1: if (100 >= 20)  ✓ проверка пройдена
Поток 2: if (100 >= 20)  ✓ проверка пройдена
Поток 1: _balance -= 20  → _balance = 80
Поток 2: _balance -= 20  → _balance = 60 (должно быть 80!)

Результат: Снято 40, хотя проверка прошла для каждого отдельно. После 6 операций вывода 120 баланс может быть отрицательным.

Решение 1: lock (Monitor) — самое простое

Читать полностью ->
Async: Producer-Consumer с использованием Channel
2.0 Middle🔥 15💬 1

Решение

Анализ задачи

System.Threading.Channels — это современная альтернатива BlockingCollection для асинхронного взаимодействия между потоками:

Ключевые компоненты:

  1. BoundedChannel — ограниченная ёмкость (10 сообщений)
  2. Producer — генерирует и отправляет сообщения
  3. Consumer — читает и обрабатывает сообщения
  4. Backpressure — автоматическое давление при переполнении
  5. CancellationToken — graceful shutdown

Модели и интерфейсы

using System;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using System.Collections.Generic;

/// <summary>
/// Сообщение для обработки
/// </summary>
public class Message
{
    public int Id { get; set; }
    public string Content { get; set; }
    public DateTime CreatedAt { get; set; }

public override string ToString() => $"[Message {Id}] {Content}";
}

Базовая реализация MessageProcessor

Читать полностью ->
Dependency Injection: Рефакторинг тесно связанного кода
2.3 Middle🔥 15💬 1

Решение

Анализ проблем: Нарушения SOLID

Код содержит множество нарушений:

  1. Single Responsibility (SRP) — OrderService отвечает за заказы, БД, email и логирование
  2. Dependency Inversion (DIP) — OrderService зависит от конкретных классов, а не интерфейсов
  3. Open/Closed (OCP) — нельзя использовать другую БД или email-сервис без изменения OrderService
  4. Interface Segregation (ISP) — нет интерфейсов для работы с зависимостями
  5. Tight Coupling — невозможно тестировать OrderService без реальной БД и email-сервиса

Проблемы:

  • Сложно писать unit-тесты (нужны реальные БД и email-сервисы)
  • Нельзя заменить SqlDatabase на другую БД без изменения кода
  • Код жёстко связан с конкретными реализациями
  • Сложно отследить все зависимости

Шаг 1: Определяем интерфейсы

/// <summary>
/// Интерфейс для работы с БД
/// </summary>
public interface IDatabase
{
    Task SaveAsync(Order order);
}
Читать полностью ->
Entity Framework: Реализация пагинации
2.0 Middle🔥 15💬 1

Решение

Анализ задачи

Пагинация — это критическая операция в Web API. Ключевые требования:

  • Эффективность — не загружать все данные в память
  • Skip/Take — правильное их применение
  • Сортировка ПЕРЕД пагинацией — обязательно
  • Минимум запросов — один для данных, один для общего количества
  • Валидация — pageNumber > 0, pageSize > 0

Структуры данных

// DTO для результата пагинации
public class PagedResult<T>
{
    public List<T> Items { get; set; }
    public int TotalCount { get; set; }
    public int PageNumber { get; set; }
    public int PageSize { get; set; }
    public int TotalPages { get; set; }
    public bool HasPreviousPage => PageNumber > 1;
    public bool HasNextPage => PageNumber < TotalPages;
}

// DTO для продукта
public class ProductDto
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public DateTime CreatedAt { get; set; }
}
Читать полностью ->
Dictionary: Реализация LRU Cache
2.3 Middle🔥 14💬 1

Решение

Анализ задачи

LRU Cache — это структура данных, которая хранит последние использованные элементы. Ключевые требования:

  1. O(1) для Get и Put — нужна Hash Table (Dictionary)
  2. O(1) для удаления самого старого — нужен двусвязный список
  3. Отслеживание порядка использования — новые элементы в конец, старые в начало

Идея: Комбинация Dictionary + LinkedList

  • Dictionary → быстрый поиск по ключу
  • LinkedList → быстрое удаление/перемещение элементов

Базовая реализация

using System;
using System.Collections.Generic;

public class LRUCache<TKey, TValue> where TKey : notnull
{
    private readonly int _capacity;
    // Dictionary: ключ -> узел в связном списке
    private readonly Dictionary<TKey, LinkedListNode<(TKey, TValue)>> _cache;
    // Двусвязный список: (ключ, значение)
    // Самый свежий элемент в конце (Last), самый старый в начале (First)
    private readonly LinkedList<(TKey, TValue)> _list;
Читать полностью ->
CQRS: Разделение команд и запросов
3.0 Senior🔥 13💬 1

CQRS: Разделение команд и запросов

Что такое CQRS?

CQRS (Command Query Responsibility Segregation) — это паттерн, который разделяет операции на две части:

  • Commands — операции, которые изменяют состояние (Create, Update, Delete)
  • Queries — операции, которые только читают данные (Get, List)

Это позволяет:

  • Оптимизировать чтение и запись отдельно
  • Использовать разные модели данных
  • Масштабировать независимо
  • Улучшить производительность

Базовые интерфейсы

// Маркер для команд
public interface ICommand { }

// Команда с результатом
public interface ICommand<out TResult> { }

// Обработчик команды без результата
public interface ICommandHandler<in TCommand> 
    where TCommand : ICommand
{
    Task HandleAsync(TCommand command);
}

// Обработчик команды с результатом
public interface ICommandHandler<in TCommand, TResult> 
    where TCommand : ICommand<TResult>
{
    Task<TResult> HandleAsync(TCommand command);
}
Читать полностью ->
Наследование и полиморфизм: Предсказать вывод
2.0 Middle🔥 13💬 1

Решение

Анализ и предсказание вывода

a1.Speak();   // "Animal speaks"
a1.Walk();    // "Animal walks"
a2.Speak();   // "Dog barks"
a2.Walk();    // "Animal walks"
d1.Speak();   // "Dog barks"
d1.Walk();    // "Dog runs"

Почему именно такой вывод? Это критический момент для понимания разницы между virtual/override и new.


Детальный разбор каждой строки

1. a1.Speak() → "Animal speaks"

Тип переменной: Animal
Объект: Animal (не Dog)
Метод: virtual, но не переопределён
Результат: вызов базовой реализации

2. a1.Walk() → "Animal walks"

Тип переменной: Animal
Объект: Animal
Метод: обычный (НЕ virtual)
Результат: вызов Animal.Walk()

3. a2.Speak() → "Dog barks"

Тип переменной: Animal (полиморфно!)
Объект: Dog
Метод: virtual + override
Результат: вызывается РЕАЛЬНО вызывается DOG.Speak()

Это полиморфизм в действии! Несмотря на то что a2 типа Animal, на runtime вызывается Dog.Speak().

Читать полностью ->
Async/Await: Предсказать вывод программы
1.8 Middle🔥 12💬 1

Решение

Что выведет программа?

Вывод: Ничего (пустая строка) или (nothing). Переменная result остаётся null.

Почему это происходит:

Это классическая ошибка при работе с async/await. Проблема в том, что:

  1. Метод SaySomething() имеет сигнатуру async Task<string> (возвращает Task)
  2. Вызов SaySomething() в методе Main() не ждёт завершения асинхронной операции
  3. Программа продолжает выполнение сразу же, не дожидаясь await Task.Delay(5)
  4. Main() синхронный метод, он не может использовать await
  5. Поток выполнения программы завершается, так как Main() закончился, до того, как задача из SaySomething() выполнится
Мегапоток выполнения:
1. Вызов SaySomething() — запускается асинхронная задача, но НЕ ЖДЁМ
2. Console.WriteLine(result) — result всё ещё null, выводим ничего
3. Main завершается → программа завершается
4. (в фоне) Задача из SaySomething() пытается выполниться, но их уже нет

Механизм async/await

Читать полностью ->
Потокобезопасный счётчик с множественными читателями
2.3 Middle🔥 12💬 1

Решение

Анализ задачи

Это классическая задача на оптимизацию под сценарий read-heavy workload (множество читателей, редкие записи). Стандартная блокировка lock заставляла бы читателей выстраиваться в очередь, что неэффективно, так как они не конфликтуют друг с другом.

Выбор механизма: ReaderWriterLockSlim

ReaderWriterLockSlim — это специализированный примитив синхронизации, идеально подходящий для этой ситуации:

Преимущества:

  • Читатели не блокируют друг друга (параллельные читатели работают одновременно)
  • Запись получает эксклюзивный доступ
  • Лучше, чем обычная lock в сценариях с большим количеством читателей
  • Встроенный механизм справедливого распределения между читателями и писателями

Когда использовать:

  • Соотношение читателей к писателям >> 1 (например, 90% читает, 10% пишет)
  • Критична производительность при параллельном чтении

Реализация с ReaderWriterLockSlim

using System;
using System.Threading;
Читать полностью ->
Unit Testing: Написать тесты для сервиса заказов
2.0 Middle🔥 11💬 1

Решение уже было отправлено

Решение для Unit Testing с xUnit и Moq содержит:

  1. Успешный сценарий — ProcessOrderAsync_WithValidOrder_ReturnsSuccess
  2. Заказ без товаров — ProcessOrderAsync_WithEmptyItems_ThrowsInvalidOrderException
  3. Ошибка оплаты — ProcessOrderAsync_WhenPaymentFails_ReturnsFailureResult
  4. Email отправляется корректно — ProcessOrderAsync_SendsEmailOnlyAfterSuccessfulPayment
  5. Email не отправляется при ошибке — ProcessOrderAsync_DoesNotSendEmail_WhenPaymentFails
  6. Корректный расчёт суммы — ProcessOrderAsync_CalculatesTotalCorrectly (Theory test)

Покрытие:

  • Arrange-Act-Assert паттерн
  • Изолированные моки для всех зависимостей
  • Проверка вызова методов через Verify
  • Theory tests для граничных случаев
Читать полностью ->
Web API: Реализация Rate Limiting
2.2 Middle🔥 11💬 1

Решение

Структуры данных и моки

Перед написанием тестов нужно определить необходимые структуры:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

// Enum для статуса заказа
public enum OrderStatus
{
    Pending = 0,
    Paid = 1,
    Shipped = 2,
    Delivered = 3,
    Cancelled = 4
}

// Модель заказа
public class Order
{
    public int Id { get; set; }
    public int CustomerId { get; set; }
    public string CustomerEmail { get; set; }
    public List<OrderItem> Items { get; set; } = new();
    public OrderStatus Status { get; set; }
}

public class OrderItem
{
    public int Id { get; set; }
    public decimal Price { get; set; }
    public int Quantity { get; set; }
}

// Результат обработки заказа
public class OrderResult
{
    public bool Success { get; set; }
    public int? OrderId { get; set; }
    public string ErrorMessage { get; set; }
Читать полностью ->
Реализация простой электронной таблицы
2.0 Middle🔥 11💬 1

Решение

Анализ задачи

Это классическая задача на обработку зависимостей и парсинг выражений. Ключевые сложности:

  1. Парсинг адресов ячеек: A1, B2, Z10 → интерпретировать
  2. Парсинг выражений: =A1+B2*2 → построить AST и вычислить
  3. Обнаружение циклов: A1=B1, B1=A1 → предотвратить бесконечную рекурсию
  4. Кэширование: Помнить уже вычисленные значения
  5. Обработка ошибок: Деление на ноль, несуществующие ячейки

Структуры данных

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

/// <summary>
/// Результат вычисления ячейки
/// </summary>
public class CellValue
{
    public bool IsError { get; set; }
    public string ErrorMessage { get; set; }
    public decimal? Value { get; set; }

public static CellValue Success(decimal value) => new() { Value = value, IsError = false };
    public static CellValue Error(string message) => new() { ErrorMessage = message, IsError = true };
}
Читать полностью ->