Решение
Анализ задачи
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;
}
}
Утечка памяти в Event Subscription
Почему происходит утечка памяти?
Проблема в паттерне event subscription:
DataProcessor подписывается на DataReceived, EventManager сохраняет ссылку на экземпляр DataProcessorprocessor выходит из области видимости, он не удаляется из памяти, потому что EventManager всё ещё держит на него ссылкуВ цикле из 10000 итераций все 10000 экземпляров DataProcessor остаются в памяти одновременно, хотя они больше не используются.
Решение 1: IDisposable паттерн
public class DataProcessor : IDisposable
{
private bool _disposed = false;
Решение: Refactoring God Class - UserManager
Анализ проблемы
Класс UserManager нарушает принцип Single Responsibility Principle (SRP). Он отвечает за 10+ различных обязанностей:
Это делает код хрупким, сложным для тестирования и модификации.
Нарушения SOLID принципов
Single Responsibility: НАРУШЕНИЕ ✗
Open/Closed: НАРУШЕНИЕ ✗
Dependency Inversion: НАРУШЕНИЕ ✗
Решение: Разбиение на специализированные сервисы
Решение
Анализ задачи
LINQ (Language Integrated Query) — это мощный инструмент для работы с коллекциями данных. Ключевые моменты:
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();
Решение
Анализ задачи
Это классическая задача на синхронизацию потоков, где требуется обеспечить упорядоченный вывод чисел. Сложность заключается в необходимости строгого чередования — нечётное число должно вывести первый поток, затем обязательно второй поток выведет чётное число, и так далее.
Выбор механизма синхронизации
Для этой задачи наиболее подходит AutoResetEvent — легковесный примитив синхронизации, который автоматически переходит в несигнализированное состояние после выхода из WaitOne(). Это идеально для синхронизации двух потоков в чередующемся режиме.
Почему именно AutoResetEvent:
Реализация
using System;
using System.Threading;
Решение
Анализ задачи
Middleware в ASP.NET Core — это компонент, который обрабатывает HTTP-запросы и ответы. Pipeline работает как матрёшка: каждое middleware может:
_next(context)Ключевые моменты:
_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;
}
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 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}");
}
Примеры опасных сценариев:
Решение
Анализ проблемы
Оригинальный запрос медленный потому что:
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 в многопоточном коде
Почему возникает race condition?
Проблема состояния в следующем:
if (_balance >= amount), но в момент после проверки другой поток может снять деньги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) — самое простое
Решение
Анализ задачи
System.Threading.Channels — это современная альтернатива BlockingCollection для асинхронного взаимодействия между потоками:
Ключевые компоненты:
Модели и интерфейсы
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
Решение
Анализ проблем: Нарушения SOLID
Код содержит множество нарушений:
Проблемы:
Шаг 1: Определяем интерфейсы
/// <summary>
/// Интерфейс для работы с БД
/// </summary>
public interface IDatabase
{
Task SaveAsync(Order order);
}
Решение
Анализ задачи
Пагинация — это критическая операция в Web API. Ключевые требования:
Структуры данных
// 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; }
}
Решение
Анализ задачи
LRU Cache — это структура данных, которая хранит последние использованные элементы. Ключевые требования:
Идея: Комбинация 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: Разделение команд и запросов
Что такое CQRS?
CQRS (Command Query Responsibility Segregation) — это паттерн, который разделяет операции на две части:
Это позволяет:
Базовые интерфейсы
// Маркер для команд
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);
}
Решение
Анализ и предсказание вывода
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.
Детальный разбор каждой строки
a1.Speak() → "Animal speaks"Тип переменной: Animal
Объект: Animal (не Dog)
Метод: virtual, но не переопределён
Результат: вызов базовой реализации
a1.Walk() → "Animal walks"Тип переменной: Animal
Объект: Animal
Метод: обычный (НЕ virtual)
Результат: вызов Animal.Walk()
a2.Speak() → "Dog barks"Тип переменной: Animal (полиморфно!)
Объект: Dog
Метод: virtual + override
Результат: вызывается РЕАЛЬНО вызывается DOG.Speak()
Это полиморфизм в действии! Несмотря на то что a2 типа Animal, на runtime вызывается Dog.Speak().
Решение
Что выведет программа?
Вывод: Ничего (пустая строка) или (nothing). Переменная result остаётся null.
Почему это происходит:
Это классическая ошибка при работе с async/await. Проблема в том, что:
SaySomething() имеет сигнатуру async Task<string> (возвращает Task)SaySomething() в методе Main() не ждёт завершения асинхронной операцииawait Task.Delay(5)Main() синхронный метод, он не может использовать awaitMain() закончился, до того, как задача из SaySomething() выполнитсяМегапоток выполнения:
1. Вызов SaySomething() — запускается асинхронная задача, но НЕ ЖДЁМ
2. Console.WriteLine(result) — result всё ещё null, выводим ничего
3. Main завершается → программа завершается
4. (в фоне) Задача из SaySomething() пытается выполниться, но их уже нет
Механизм async/await
Решение
Анализ задачи
Это классическая задача на оптимизацию под сценарий read-heavy workload (множество читателей, редкие записи). Стандартная блокировка lock заставляла бы читателей выстраиваться в очередь, что неэффективно, так как они не конфликтуют друг с другом.
Выбор механизма: ReaderWriterLockSlim
ReaderWriterLockSlim — это специализированный примитив синхронизации, идеально подходящий для этой ситуации:
Преимущества:
lock в сценариях с большим количеством читателейКогда использовать:
Реализация с ReaderWriterLockSlim
using System;
using System.Threading;
Решение уже было отправлено
Решение для Unit Testing с xUnit и Moq содержит:
Покрытие:
Решение
Структуры данных и моки
Перед написанием тестов нужно определить необходимые структуры:
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; }
Решение
Анализ задачи
Это классическая задача на обработку зависимостей и парсинг выражений. Ключевые сложности:
Структуры данных
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 };
}