Библиотеки, которые реально используют в Microsoft.
Надоело, что на каждом углу советуют AutoMapper и Polly? Вот инструменты, которые инженеры Microsoft юзают в продакшене. Они прошли боевые условия, экономят память и закрывают задачи, которые масштабируются до уровня Bing и Azure.
1. Microsoft.IO.RecyclableMemoryStream
Проблема:
Когда в API гоняется большой трафик или крутятся экспортные задания, обычный new MemoryStream() вызывает фрагментацию LOH (Large Object Heap) и частые остановки GC. В проде типа Bing это легко выливается в
OutOfMemoryException.
Решение:
RecyclableMemoryStream:
- переиспользует буферы через пул,
- снижает аллокации и не лезет в LOH,
- даёт нормальную диагностику по памяти.
Где уместно:
ASP.NET API, которые возвращают PDF, изображения, Excel,
- фоновые задачи, работающие с файлами,
- бэкенды под gRPC и SignalR.
До:
После:
Бенч:
2. LoggerMessage (генератор кода)
Проблема:
В сервисах с высоким трафиком даже такие безобидные логи:
создают проблемы:
- лишние аллокации из-за интерполяции строк,
- боксинг значимых типов,
- нагруженный GC и просадка пропускной способности.
На масштабе Azure это превращается в задержки и прожорливость по памяти.
Решение:
LoggerMessage из .NET 8 генерирует методы на этапе компиляции:
- без интерполяции и упаковки типов,
- со структурированным форматированием через Span<T>,
- с IL без лишних аллокаций,
в 5–10 раз быстрее обычного ILogger.LogInformation(...).
Где уместно:
- нагруженные веб-API,
- воркфлоу/ивент-хендлеры, где логов много,
- телеметрия и критичные hot-path’ы.
До (аллокации):
После (генерация):
Можно вынести такие методы в статические хелперы и шарить между сервисами.
👉 @KodBlog
Надоело, что на каждом углу советуют AutoMapper и Polly? Вот инструменты, которые инженеры Microsoft юзают в продакшене. Они прошли боевые условия, экономят память и закрывают задачи, которые масштабируются до уровня Bing и Azure.
1. Microsoft.IO.RecyclableMemoryStream
Проблема:
Когда в API гоняется большой трафик или крутятся экспортные задания, обычный new MemoryStream() вызывает фрагментацию LOH (Large Object Heap) и частые остановки GC. В проде типа Bing это легко выливается в
OutOfMemoryException.
Решение:
RecyclableMemoryStream:
- переиспользует буферы через пул,
- снижает аллокации и не лезет в LOH,
- даёт нормальную диагностику по памяти.
Где уместно:
ASP.NET API, которые возвращают PDF, изображения, Excel,
- фоновые задачи, работающие с файлами,
- бэкенды под gRPC и SignalR.
До:
using var ms = new MemoryStream();
После:
var mgr = new RecyclableMemoryStreamManager();
using var ms = mgr.GetStream();
Бенч:
Method | Memory | Gen 0 | Gen 2
---------------- | -------| ----- | -----
MemoryStream | 85 KB | 1 | 1
RecyclableMemory | 2 KB | 0 | 0
2. LoggerMessage (генератор кода)
Проблема:
В сервисах с высоким трафиком даже такие безобидные логи:
_logger.LogInformation($"Обработка заказа {orderId}");создают проблемы:
- лишние аллокации из-за интерполяции строк,
- боксинг значимых типов,
- нагруженный GC и просадка пропускной способности.
На масштабе Azure это превращается в задержки и прожорливость по памяти.
Решение:
LoggerMessage из .NET 8 генерирует методы на этапе компиляции:
- без интерполяции и упаковки типов,
- со структурированным форматированием через Span<T>,
- с IL без лишних аллокаций,
в 5–10 раз быстрее обычного ILogger.LogInformation(...).
Где уместно:
- нагруженные веб-API,
- воркфлоу/ивент-хендлеры, где логов много,
- телеметрия и критичные hot-path’ы.
До (аллокации):
_logger.LogInformation($"Обработка запроса {orderId}");После (генерация):
[LoggerMessage(EventId = 100, Level = LogLevel.Information, Message = "Обработка запроса {OrderId}")]
partial void LogProcessingOrder(int orderId);
// вызов
LogProcessingOrder(orderId);Можно вынести такие методы в статические хелперы и шарить между сервисами.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥22❤6👍3👎1🤣1
C# Portal | Программирование
Библиотеки, которые реально используют в Microsoft. Надоело, что на каждом углу советуют AutoMapper и Polly? Вот инструменты, которые инженеры Microsoft юзают в продакшене. Они прошли боевые условия, экономят память и закрывают задачи, которые масштабируются…
Ещё стоит упомянуть:
3. Microsoft.SemanticKernel
Проблема: Обычных запросов и ответов уже не хватает для продакшн-приложений на ИИ. Нужны память между диалогами, разбиение целей на шаги и доступ к API вроде Microsoft 365 Copilot. Реализовать всё это вручную в .NET — тяжело и долго.
Решение:
SemanticKernel — официальный SDK от Microsoft для сборки похожих на агентов ИИ-процессов. Он даёт:
* Вызов инструментов и плагинов (C# или OpenAPI);
* Оркестрацию цепочек запросов;
* Долговременную память между вызовами;
* Планировщик для разбиения задач на подзадачи.
Когда стоит брать:
* Корпоративные ИИ-ассистенты (CRM, генерация отчётов и т.п.);
* ИИ-процессы, которые ходят во внутренние API/функции;
* Многошаговые чат-боты с планированием и памятью.
До (одиночный запрос):
После (оркестрация через SemanticKernel):
Можно регистрировать плагины, которые вызывают API, делают авторизацию, исполняют SQL или дергают другие сервисы — и всё из .NET. Документация
4. System.Threading.Channels + IAsyncEnumerable
Проблема: BlockingCollection, ConcurrentQueue и кастомные очереди в паттерне «производитель/потребитель» часто упираются в блокировки, нестабильность и проблемы под нагрузкой. Особенно в телеметрии и потоковых пайплайнах.
Решение:
* Быстрый обмен сообщениями внутри процесса;
* Контроль пропускной способности через ограниченные каналы;
* Простую связку с IAsyncEnumerable<T>;
* Асинхронный стриминг без лишних выделений памяти.
Изначально жило внутри SignalR и gRPC, сейчас это нормальный способ собирать отказоустойчивые асинхронные конвейеры в .NET.
Когда использовать:
* Сбор телеметрии и обработка метрик;
* Асинхронные пайплайны логов/событий;
* Очередеподобное поведение без внешней брокерской инфраструктуры;
* Изоляция перегородками (Bulkhead) в фоновых сервисах.
До (BlockingCollection или Queue<T>):
После (каналы и async-потоки):
Отлично работает из коробки с await foreach и подходит для фоновых задач с I/O, стриминговых API и повторяющихся пайплайнов.
👉 @KodBlog
3. Microsoft.SemanticKernel
Проблема: Обычных запросов и ответов уже не хватает для продакшн-приложений на ИИ. Нужны память между диалогами, разбиение целей на шаги и доступ к API вроде Microsoft 365 Copilot. Реализовать всё это вручную в .NET — тяжело и долго.
Решение:
SemanticKernel — официальный SDK от Microsoft для сборки похожих на агентов ИИ-процессов. Он даёт:
* Вызов инструментов и плагинов (C# или OpenAPI);
* Оркестрацию цепочек запросов;
* Долговременную память между вызовами;
* Планировщик для разбиения задач на подзадачи.
Когда стоит брать:
* Корпоративные ИИ-ассистенты (CRM, генерация отчётов и т.п.);
* ИИ-процессы, которые ходят во внутренние API/функции;
* Многошаговые чат-боты с планированием и памятью.
До (одиночный запрос):
var result = await openAiClient
.GetCompletionAsync("Какая погода в Москве?");
После (оркестрация через SemanticKernel):
var kernel = Kernel.CreateBuilder().Build();
var prompt = kernel
.CreateFunctionFromPrompt("Какая погода в Москве?");
var result = await prompt.InvokeAsync();
Можно регистрировать плагины, которые вызывают API, делают авторизацию, исполняют SQL или дергают другие сервисы — и всё из .NET. Документация
4. System.Threading.Channels + IAsyncEnumerable
Проблема: BlockingCollection, ConcurrentQueue и кастомные очереди в паттерне «производитель/потребитель» часто упираются в блокировки, нестабильность и проблемы под нагрузкой. Особенно в телеметрии и потоковых пайплайнах.
Решение:
System.Threading.Channels даёт:* Быстрый обмен сообщениями внутри процесса;
* Контроль пропускной способности через ограниченные каналы;
* Простую связку с IAsyncEnumerable<T>;
* Асинхронный стриминг без лишних выделений памяти.
Изначально жило внутри SignalR и gRPC, сейчас это нормальный способ собирать отказоустойчивые асинхронные конвейеры в .NET.
Когда использовать:
* Сбор телеметрии и обработка метрик;
* Асинхронные пайплайны логов/событий;
* Очередеподобное поведение без внешней брокерской инфраструктуры;
* Изоляция перегородками (Bulkhead) в фоновых сервисах.
До (BlockingCollection или Queue<T>):
var queue = new Queue<int>();
lock (queue) { queue.Enqueue(42); }
int item;
lock (queue) { item = queue.Dequeue(); }
После (каналы и async-потоки):
var ch = Channel.CreateUnbounded<int>();
await ch.Writer.WriteAsync(42);
await foreach (var item in ch.Reader.ReadAllAsync())
Console.WriteLine(item);
Отлично работает из коробки с await foreach и подходит для фоновых задач с I/O, стриминговых API и повторяющихся пайплайнов.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤7👍3
Может кому понадобится: проект генерирует архитектурные диаграммы из кода.
https://github.com/likec4/likec4/🤨
👉 @KodBlog
https://github.com/likec4/likec4/
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10🤣2❤1
Как компилятор выводит тип из
Разберём простые варианты:
Тут всё понятно. Но что происходит в
Кажется логичным ожидать, что
Для этого конкретного примера разницы нет — и
Тип
Конструктор
В итоге
Если же явно указать тип в
То результатом
👉 @KodBlog
default?default в C# штука мощная. Для ссылочных типов он даёт null, для значимых — нулевое значение. Забавно, что default(T) и new T() для структур могут вести себя по-разному. Раньше в C# нужно было писать default(T), а сейчас, если тип очевиден для компилятора, можно оставить просто default. Но как именно компилятор понимает, что за тип там должен быть? И всегда ли можно доверять этому выводу?Разберём простые варианты:
// Тип тянется из левой части
int foo = default;
// Тип тянется из параметра
Foo(default);
void Foo(int bar) => throw null;
Тут всё понятно. Но что происходит в
switch-выражении?var sample = new byte[0];
ReadOnlyMemory<byte> foo = sample switch
{
byte[] value => value,
_ => default
};
Кажется логичным ожидать, что
default станет default(ReadOnlyMemory<byte>), ведь слева стоит такой тип. Но нет — фактически это будет default(byte[]). Компилятор берёт тип не из левой части, а из веток switch и пытается вывести общий тип. В данном случае общий тип — byte[], значит и default станет byte[].Для этого конкретного примера разницы нет — и
default(byte[]), и default(ReadOnlyMemory<byte>) в итоге дадут совместимый результат. Но последствия могут всплыть позже. Например, если заменить тип на nullable:var sample = new object();
ReadOnlyMemory<byte>? foo = sample switch
{
byte[] value => value,
_ => default
};
Тип
default снова выводится как default(byte[]), то есть null. А дальше начинает работать неявное преобразование:// из исходников .NET 9
public static implicit operator ReadOnlyMemory<T>(T[]? array)
=> new ReadOnlyMemory<T>(array);
Конструктор
ReadOnlyMemory<T>, который принимает T[]?, при null выдаёт пустое значение:// из исходников .NET 9
public ReadOnlyMemory(T[]? array)
{
if (array == null)
{
this = default;
return;
}
…
}
В итоге
foo окажется пустым ReadOnlyMemory<byte> — не null. То есть foo.HasValue будет true.Если же явно указать тип в
default:object sample = new();
ReadOnlyMemory<byte>? foo = sample switch
{
byte[] value => value,
_ => default(ReadOnlyMemory<byte>?)
};
То результатом
switch будет именно null, и foo.HasValue станет false.Please open Telegram to view this post
VIEW IN TELEGRAM
⚡6❤2
Самые частые способы уменьшить размер Docker-образа
Сохраните себе — это реально частый вопрос на собесах.
• Использовать минимальную базу
Брать
• Multi-stage сборка
Собрать всё тяжёлое и зависимости в одном этапе, а в финальный образ скопировать только готовые артефакты.
• Меньше слоёв
Группировать команды в один
• Убирать зависимости для сборки
Компиляторы, менеджеры пакетов и прочий мусор — только на build-стейдже, не в рантайме.
• Чистить кеш пакетного менеджера
Чистить
• .dockerignore
Не тащить лишнее в образ:
• Не копировать всё подряд
Вместо
• Меньше и легче рантайм
Статические бинарники, минимальные рантаймы, без лишних зависимостей.
• Чистить бинарники и ресурсы
Убирать отладочную информацию, неиспользуемые ассеты и т.п.
• Настраивать права сразу
Ставить
👉 @KodBlog
Сохраните себе — это реально частый вопрос на собесах.
• Использовать минимальную базу
Брать
alpine, distroless или slim, а не полноценные образы с целой ОС.• Multi-stage сборка
Собрать всё тяжёлое и зависимости в одном этапе, а в финальный образ скопировать только готовые артефакты.
• Меньше слоёв
Группировать команды в один
RUN, когда это уместно, и не плодить лишние слои.• Убирать зависимости для сборки
Компиляторы, менеджеры пакетов и прочий мусор — только на build-стейдже, не в рантайме.
• Чистить кеш пакетного менеджера
Чистить
apt, yum, apk и прочее в том же слое, где ставятся пакеты.• .dockerignore
Не тащить лишнее в образ:
.git, тесты, доки, локальные конфиги и т.д.• Не копировать всё подряд
Вместо
COPY . . копировать только то, что реально нужно для приложения.• Меньше и легче рантайм
Статические бинарники, минимальные рантаймы, без лишних зависимостей.
• Чистить бинарники и ресурсы
Убирать отладочную информацию, неиспользуемые ассеты и т.п.
• Настраивать права сразу
Ставить
chmod/chown рано, чтобы не плодить лишние слои с изменёнными разрешениями.Please open Telegram to view this post
VIEW IN TELEGRAM
🔥6❤3👍1
Уходим от анемичных моделей. Пример DDD-рефакторинга.
Если работали с древней кодовой базой на C#, то наверняка видели анемичную доменную модель. Открываешь какой-нибудь OrderService и ловишь фейспалм: в одном классе и ценообразование, и скидки, и проверка склада, и работа с БД. Оно вроде крутится, пока продукт маленький, но дальше каждая новая фича превращается в рулетку с регрессиями. Тесты тоже летят к черту, потому что доменная логика спрятана под инфраструктурой.
Классика анемичного домена: сущности как DTO с геттерами и сеттерами, а вся логика в сервисах. Понимать систему тяжело, любое изменение наугад, а поведение расползлось по слоям.
В этой серии мы:
* посмотрим на типичную анемичную реализацию
* найдем скрытые бизнес-правила, которые делают её хрупкой
* шаг за шагом переложим ответственность в агрегат и дадим ему нормальное поведение
* разберем выгоды, чтобы можно было аргументированно защитить изменения в команде
Старт. Божественный OrderService
Ниже довольно жизненный пример. Помимо подсчета итоговой суммы он:
* навешивает VIP-скидку 5 процентов
* падает, если товара нет на складе
* отклоняет заказ, если клиент вышел за кредитный лимит
Что здесь рыхлое
1. Правила размазаны по сервису. Скидки, склад, кредит — всё в кучу. Чтобы понять бизнес-логику, нужно разбирать инфраструктурный код.
2. Жесткая связка. OrderService тащит в себя знание о ценах, остатках и EF Core, хотя цель — просто разместить заказ.
3. Больно тестировать. Для юнит-теста нужно мокать БД, ценообразование, склад и сценарии для VIP и не VIP. Тесты получаются хрупкие и громоздкие.
Наша цель — втащить правила в домен, чтобы приложение занималось оркестрацией, а не бизнес-логикой.
Принципы рефакторинга
1. Инварианты держим рядом с данными. Проверки склада, скидки, кредит — в агрегате Order, а не в сервисе сверху.
2. Слой приложения читался как сценарий. Хочется "разместить заказ", а не "подсчитать сумму, проверить лимит, сохранить в БД".
3. Рефакторим кусками. Маленькие шаги, компилируемые на каждом этапе. Никаких переписываний с нуля.
4. Баланс чистоты и практичности. Меняем только там, где выигрыш в читабельности, безопасности и тестируемости действительно окупает вложения.
👉 @KodBlog
Если работали с древней кодовой базой на C#, то наверняка видели анемичную доменную модель. Открываешь какой-нибудь OrderService и ловишь фейспалм: в одном классе и ценообразование, и скидки, и проверка склада, и работа с БД. Оно вроде крутится, пока продукт маленький, но дальше каждая новая фича превращается в рулетку с регрессиями. Тесты тоже летят к черту, потому что доменная логика спрятана под инфраструктурой.
Классика анемичного домена: сущности как DTO с геттерами и сеттерами, а вся логика в сервисах. Понимать систему тяжело, любое изменение наугад, а поведение расползлось по слоям.
В этой серии мы:
* посмотрим на типичную анемичную реализацию
* найдем скрытые бизнес-правила, которые делают её хрупкой
* шаг за шагом переложим ответственность в агрегат и дадим ему нормальное поведение
* разберем выгоды, чтобы можно было аргументированно защитить изменения в команде
Старт. Божественный OrderService
Ниже довольно жизненный пример. Помимо подсчета итоговой суммы он:
* навешивает VIP-скидку 5 процентов
* падает, если товара нет на складе
* отклоняет заказ, если клиент вышел за кредитный лимит
// OrderService.cs
public void PlaceOrder(
Guid customerId,
IEnumerable<OrderItemDto> items)
{
var customer = _db.Customers.Find(customerId);
if (customer is null)
throw new ArgumentException("Клиент не найден");
var order = new Order { CustomerId = customerId };
foreach (var dto in items)
{
var inventory = _invService.GetStock(dto.ProductId);
if (inventory < dto.Quantity)
throw new InvalidOperationException("Товара недостаточно");
var price = _pricingService.GetPrice(dto.ProductId);
var lineTotal = price * dto.Quantity;
if (customer.IsVip)
lineTotal *= 0.95m;
order.Items.Add(new OrderItem
{
ProductId = dto.ProductId,
Quantity = dto.Quantity,
UnitPrice = price,
LineTotal = lineTotal
});
}
order.Total = order.Items.Sum(i => i.LineTotal);
if (customer.CreditUsed + order.Total > customer.CreditLimit)
throw new InvalidOperationException("Кредитный лимит превышен");
_db.Orders.Add(order);
_db.SaveChanges();
}
Что здесь рыхлое
1. Правила размазаны по сервису. Скидки, склад, кредит — всё в кучу. Чтобы понять бизнес-логику, нужно разбирать инфраструктурный код.
2. Жесткая связка. OrderService тащит в себя знание о ценах, остатках и EF Core, хотя цель — просто разместить заказ.
3. Больно тестировать. Для юнит-теста нужно мокать БД, ценообразование, склад и сценарии для VIP и не VIP. Тесты получаются хрупкие и громоздкие.
Наша цель — втащить правила в домен, чтобы приложение занималось оркестрацией, а не бизнес-логикой.
Принципы рефакторинга
1. Инварианты держим рядом с данными. Проверки склада, скидки, кредит — в агрегате Order, а не в сервисе сверху.
2. Слой приложения читался как сценарий. Хочется "разместить заказ", а не "подсчитать сумму, проверить лимит, сохранить в БД".
3. Рефакторим кусками. Маленькие шаги, компилируемые на каждом этапе. Никаких переписываний с нуля.
4. Баланс чистоты и практичности. Меняем только там, где выигрыш в читабельности, безопасности и тестируемости действительно окупает вложения.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤12
C# Portal | Программирование
Уходим от анемичных моделей. Пример DDD-рефакторинга. Если работали с древней кодовой базой на C#, то наверняка видели анемичную доменную модель. Открываешь какой-нибудь OrderService и ловишь фейспалм: в одном классе и ценообразование, и скидки, и проверка…
Пример DDD-рефакторинга.
Пошаговый рефакторинг
Цель здесь не в догонялках за академической чистотой или «идеальным» DDD. Идея в том, чтобы постепенно усилить связность и дать домену пространство для нормального выражения своей логики. На каждом шаге задаём простой вопрос: это поведение должно жить в домене? Если да — переносим его туда.
Внедрение логики создания и проверки
Первый шаг — заставить агрегат сам собирать себя. Статический метод Create становится единой точкой входа, где инварианты сразу проверяются по принципу fail fast.
Проверка остатков прямо в Order делает код тестируемее, но жёстко привязывает поток создания заказа к доступности товара. Не всегда это хорошо: в некоторых доменах правильнее оформлять это через доменное событие и проверять асинхронно.
Зачем это всё?
Теперь заказ сразу падает с ошибкой, если нарушен какой-то инвариант. Сервис больше не занимается микроменеджментом скидок или склада — всё это живёт внутри домена.
Обратите внимание, как мы теперь следуем принципу «Скажи, не спрашивай». Вместо того, чтобы сервис проверял условия и затем манипулировал заказом, мы говорим заказу создать себя с необходимыми встроенными проверками. Это фундаментальный сдвиг в сторону инкапсуляции.
Замечание о передаче сервисов в методы домена
Передача зависимостей вроде IPricingService или IInventoryService в доменный метод Order.Create может выглядеть нестандартно, но это осмысленное решение. Так мы оставляем оркестрацию в домене, а не превращаем app-сервис в procedural god-object.
Зависимости передаются явно, без скрытых контейнеров внутри сущности. Подход пушка, но применять его стоит там, где операция реально принадлежит домену и выигрывает от прямого доступа к сервисам.
👉 @KodBlog
Пошаговый рефакторинг
Цель здесь не в догонялках за академической чистотой или «идеальным» DDD. Идея в том, чтобы постепенно усилить связность и дать домену пространство для нормального выражения своей логики. На каждом шаге задаём простой вопрос: это поведение должно жить в домене? Если да — переносим его туда.
Внедрение логики создания и проверки
Первый шаг — заставить агрегат сам собирать себя. Статический метод Create становится единой точкой входа, где инварианты сразу проверяются по принципу fail fast.
Проверка остатков прямо в Order делает код тестируемее, но жёстко привязывает поток создания заказа к доступности товара. Не всегда это хорошо: в некоторых доменах правильнее оформлять это через доменное событие и проверять асинхронно.
// Order.cs (фабричный метод)
public static Order Create(
Customer customer,
IEnumerable<(Guid productId, int quantity)> lines,
IPricingService pricingService,
IInventoryService invService)
{
var order = new Order(customer.Id);
foreach (var (id, quantity) in lines)
{
if (invService.GetStock(id) < quantity)
throw new InvalidOperationException("Товара недостаточно");
var price = pricingService.GetPrice(id);
order.AddItem(id, quantity, price, customer.IsVip);
}
order.EnsureCreditWithinLimit(customer);
return order;
}
Зачем это всё?
Теперь заказ сразу падает с ошибкой, если нарушен какой-то инвариант. Сервис больше не занимается микроменеджментом скидок или склада — всё это живёт внутри домена.
Обратите внимание, как мы теперь следуем принципу «Скажи, не спрашивай». Вместо того, чтобы сервис проверял условия и затем манипулировал заказом, мы говорим заказу создать себя с необходимыми встроенными проверками. Это фундаментальный сдвиг в сторону инкапсуляции.
Замечание о передаче сервисов в методы домена
Передача зависимостей вроде IPricingService или IInventoryService в доменный метод Order.Create может выглядеть нестандартно, но это осмысленное решение. Так мы оставляем оркестрацию в домене, а не превращаем app-сервис в procedural god-object.
Зависимости передаются явно, без скрытых контейнеров внутри сущности. Подход пушка, но применять его стоит там, где операция реально принадлежит домену и выигрывает от прямого доступа к сервисам.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8👍3🤔1
Принёс вам классный ресурс для изучения паттернов проектирования
На сайте Refactoring Guru собрали примеры для каждого паттерна на таких языках, как C#, Java, Python, PHP, Rust и ещё куча других.
Примеры суперпонятные, с кодом и пояснениями.
👉 @KodBlog
На сайте Refactoring Guru собрали примеры для каждого паттерна на таких языках, как C#, Java, Python, PHP, Rust и ещё куча других.
Примеры суперпонятные, с кодом и пояснениями.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11
Тестирование текущего времени с помощью TimeProvider и FakeTimeProvider
В тестах тяжело работать с вещами, которые зависят от конкретных данных. Представьте файловую систему: чтобы тесты корректно отрабатывали, нужно гарантировать, что структура файлов именно такая, как ожидается. С датами и временем та же проблема. Если вы пишете тесты, завязанные на текущую дату, при следующем запуске они просто упадут. Поэтому такие зависимости нужно абстрагировать, чтобы их можно было нормально тестировать.
В этом посте разберем класс TimeProvider, как его использовать и как подменять в тестах.
Раньше: ручной интерфейс
Раньше самый простой способ абстрагироваться от работы с датой и временем был в том, чтобы вручную завести интерфейс или абстрактный класс для доступа к текущему времени:
И его стандартную реализацию, которая возвращает время в UTC:
Либо похожий вариант через абстрактный класс:
Дальше этот класс просто регистрировался в DI-контейнере, и можно было использовать. Минус очевидный: в каждом проекте приходилось писать одно и то же.
Сейчас: класс TimeProvider
Начиная с .NET 8, команда .NET добавила абстрактный класс TimeProvider. Он не только даёт абстракцию над локальным временем, но и предоставляет методы для работы с высокоточными временными метками и часовыми поясами.
Важно: даты возвращаются в виде DateTimeOffset, а не DateTime.
TimeProvider доступен из коробки в консольных приложениях .NET:
Если вы используете dependency injection, его достаточно зарегистрировать как синглтон:
Пример использования:
Тестирование TimeProvider
Для тестов можно использовать NuGet-пакет
С помощью FakeTimeProvider можно вручную задавать текущее UTC-время, локальное время и другие параметры, которые предоставляет TimeProvider:
На самом деле TimeProvider умеет намного больше, чем просто возвращать UTC и локальное время. Подробности можно посмотреть в официальной документации
👉 @KodBlog
В тестах тяжело работать с вещами, которые зависят от конкретных данных. Представьте файловую систему: чтобы тесты корректно отрабатывали, нужно гарантировать, что структура файлов именно такая, как ожидается. С датами и временем та же проблема. Если вы пишете тесты, завязанные на текущую дату, при следующем запуске они просто упадут. Поэтому такие зависимости нужно абстрагировать, чтобы их можно было нормально тестировать.
В этом посте разберем класс TimeProvider, как его использовать и как подменять в тестах.
Раньше: ручной интерфейс
Раньше самый простой способ абстрагироваться от работы с датой и временем был в том, чтобы вручную завести интерфейс или абстрактный класс для доступа к текущему времени:
public interface IDateTimeWrapper
{
DateTime GetCurrentDate();
}
И его стандартную реализацию, которая возвращает время в UTC:
public class DateTimeWrapper : IDateTimeWrapper
{
public DateTime GetCurrentDate()
=> DateTime.UtcNow;
}
Либо похожий вариант через абстрактный класс:
public abstract class DateTimeWrapper
{
public virtual DateTime GetCurrentDate() => DateTime.UctNow;
}
Дальше этот класс просто регистрировался в DI-контейнере, и можно было использовать. Минус очевидный: в каждом проекте приходилось писать одно и то же.
Сейчас: класс TimeProvider
Начиная с .NET 8, команда .NET добавила абстрактный класс TimeProvider. Он не только даёт абстракцию над локальным временем, но и предоставляет методы для работы с высокоточными временными метками и часовыми поясами.
Важно: даты возвращаются в виде DateTimeOffset, а не DateTime.
TimeProvider доступен из коробки в консольных приложениях .NET:
DateTimeOffset utc = TimeProvider.System.GetUtcNow();
Console.WriteLine(utc);
DateTimeOffset local = TimeProvider.System.GetLocalNow();
Console.WriteLine(local);
Если вы используете dependency injection, его достаточно зарегистрировать как синглтон:
builder.Services.AddSingleton(TimeProvider.System);
Пример использования:
public class Vacation(TimeProvider _time)
{
public bool IsVacation
=> _time.GetLocalNow().Month == 8;
}
Тестирование TimeProvider
Для тестов можно использовать NuGet-пакет
Microsoft.Extensions.TimeProvider.Testing, в котором есть класс FakeTimeProvider. Это заглушка для абстрактного TimeProvider.С помощью FakeTimeProvider можно вручную задавать текущее UTC-время, локальное время и другие параметры, которые предоставляет TimeProvider:
[Fact]
public void WhenItsAugust_ShouldReturnTrue()
{
// Arrange
var fakeTime = new FakeTimeProvider();
fakeTime.SetUtcNow(
new DateTimeOffset(2025, 8, 14,
22, 24, 12, TimeSpan.Zero));
var sut = new Vacation(fakeTime);
Assert.True(sut.IsVacation);
}
На самом деле TimeProvider умеет намного больше, чем просто возвращать UTC и локальное время. Подробности можно посмотреть в официальной документации
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9❤3
Настоятельно рекомендую фри PDF-книгу:
Algorithms от Jeff Erickson — с кучей наглядных иллюстраций и упражнений.
👉 @KodBlog
Algorithms от Jeff Erickson — с кучей наглядных иллюстраций и упражнений.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8😁1
Создание параметризованных тестов в xUnit
xUnit использует атрибуты для объявления тестовых методов.
Атрибут Fact описывает обычный тест, а Theory — параметризованный.
Допустим, у нас есть тест, который проверяет, что email-парсер корректно извлекает домен:
Рассмотрим четыре способа написания параметризованных тестов.
1. InlineData
Самый простой вариант — передать тестовые данные напрямую через атрибут:
В
Минус в том, что при большом количестве кейсов код быстро разрастается. Плюс можно использовать только константные значения параметров.
2. MemberData
В атрибуте
3. ClassData
Тестовые данные загружаются из класса, который реализует
Подход более громоздкий — нужно вручную реализовывать
4. TheoryData
Пример с использованием
Для добавления одного тестового кейса вызывается
👉 @KodBlog
xUnit использует атрибуты для объявления тестовых методов.
Атрибут Fact описывает обычный тест, а Theory — параметризованный.
Допустим, у нас есть тест, который проверяет, что email-парсер корректно извлекает домен:
public void Should_Return_Domain(
string email, string expectedDomain)
{
var parser = new EmailParser();
var domain = parser.GetDomain(email);
Assert.Equal(domain, expectedDomain);
}
Рассмотрим четыре способа написания параметризованных тестов.
1. InlineData
Самый простой вариант — передать тестовые данные напрямую через атрибут:
[Theory]
[InlineData("test@test.com", "test.com")]
[InlineData("user@github.io", "github.io")]
public void Should_Return_Domain(
string email, string expectedDomain)
{ … }
В
InlineData мы передаём значения аргументов тестируемого метода и ожидаемый результат. Атрибут можно указывать сколько угодно раз — по одному на каждый тестовый кейс.Минус в том, что при большом количестве кейсов код быстро разрастается. Плюс можно использовать только константные значения параметров.
2. MemberData
MemberData позволяет программно задавать тестовые данные через статическое свойство или метод:[Theory]
[MemberData(nameof(EmailTestData))]
public void Should_Return_Domain(
string email, string expectedDomain)
{ … }
public static IEnumerable<object[]>
EmailTestData => new List<object>
{
new object[] { "test@test.com", "test.com" },
new object[] { "user@github.io", "github.io" }
};
В атрибуте
MemberData указывается имя члена. Ограничение здесь одно — свойство или метод должны возвращать IEnumerable<object[]>, из-за чего строгая типизация отсутствует.3. ClassData
ClassData выносит тестовые данные в отдельный класс. Это удобно для структурирования данных и повторного использования.Тестовые данные загружаются из класса, который реализует
IEnumerable<object[]>:[Theory]
[ClassData(typeof(EmailTestData))]
public void Should_Return_Domain(
string email, string expectedDomain)
{ … }
public class EmailTestData : IEnumerable<object[]>
{
public IEnumerable<object[]> GetEnumerator()
{
yield return new object[] {
"test@test.com", "test.com" };
yield return new object[] {
"user@github.io", "github.io" };
}
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
}
Подход более громоздкий — нужно вручную реализовывать
IEnumerable. При этом проблема с типобезопасностью всё ещё остаётся.4. TheoryData
TheoryData позволяет хранить тестовые данные с сохранением строгой типизации.Пример с использованием
ClassData (аналогично можно применять и с MemberData, возвращая TheoryData из свойства или метода):[Theory]
[ClassData(typeof(EmailTestData))]
public void Should_Return_Domain(
string email, string expectedDomain)
{ … }
public class EmailTestData :
TheoryData<string, string>
{
public EmailTestData()
{
Add("test@test.com", "test.com");
Add("user@github.io", "github.io");
}
}
TheoryData — это обобщённый класс, в котором явно задаются типы параметров теста.Для добавления одного тестового кейса вызывается
Add, а новые сценарии просто добавляются дополнительными вызовами этого метода.Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3❤1👍1
This media is not supported in your browser
VIEW IN TELEGRAM
Это аккуратный и чистый способ работать с Git из терминала
Froggit — минималистичный терминальный UI для Git. Он помогает стейджить файлы, делать коммиты, управлять ветками и смотреть логи без необходимости заучивать сложные команды.
Всё управляется с клавиатуры, работает быстро и без лишних отвлекающих элементов, поэтому проще оставаться в потоке при работе с Git.
Тестим и оцениваем😁
👉 @KodBlog
Froggit — минималистичный терминальный UI для Git. Он помогает стейджить файлы, делать коммиты, управлять ветками и смотреть логи без необходимости заучивать сложные команды.
Всё управляется с клавиатуры, работает быстро и без лишних отвлекающих элементов, поэтому проще оставаться в потоке при работе с Git.
Тестим и оцениваем
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4👏2
Разбираем генераторы исходного кода.
Генераторы исходного кода, появившиеся в C# 9, стали мощным инструментом метапрограммирования в .NET. Они позволяют разработчикам автоматически генерировать дополнительный C#-код. Генераторы выполняются на этапе компиляции: анализируют существующий код и создают новые файлы исходников. Эти файлы затем компилируются вместе с остальной кодовой базой, что даёт возможность динамически расширять проект на основе уже написанного кода.
Генераторы исходного кода можно использовать для разных задач: скафолдинга, валидации, повышения читаемости и упрощения поддержки кода.
В C# 12 генераторы получили дальнейшее развитие. Появилась поддержка более сложных сценариев и выросла производительность за счёт уменьшения шаблонного кода и более эффективных проверок на этапе компиляции.
1. Инкрементные генераторы
Инкрементные генераторы регенерируют код только тогда, когда это действительно нужно. Они кэшируют результаты предыдущих запусков и пересобирают сгенерированный код только при изменении входных данных, что заметно ускоряет сборку.
2. Анализ зависимостей исходного кода
Компилятор точнее определяет, какие части проекта зависят от сгенерированного кода. В результате сборки становятся эффективнее, а количество лишних перекомпиляций сокращается.
3. Улучшенная диагностика
Ошибки и предупреждения можно более точно сообщать прямо через сгенерированный код. Разработчик получает понятную обратную связь во время разработки, что уп shows упрощает отладку и сопровождение генераторов.
4. Улучшения API Roslyn
API Roslyn получил дополнительные возможности для анализа и модификации синтаксического дерева, что позволяет реализовывать более сложную логику генерации кода.
Простой пример генератора кода
1. Настройка проекта
Создадим проект библиотеки и установим необходимые NuGet-пакеты:
2. Генератор кода
Нам понадобится класс, реализующий интерфейс IIncrementalGenerator:
Этот генератор добавляет в проект класс HelloWorld с методом SayHello. Сгенерированный код компилируется вместе с основным проектом, поэтому метод HelloGenerated.HelloWorld.SayHello() можно вызывать напрямую из вашего кода.
3. Интеграция с проектом
Создадим консольное приложение:
Добавим ссылку на проект генератора:
Обратите внимание на два нестандартных атрибута:
OutputItemType="Analyzer" — указывает, что проект должен использоваться как анализатор;
ReferenceOutputAssembly="false" — гарантирует, что целевой проект не будет ссылаться на DLL генератора во время компиляции.
После этого сгенерированный класс HelloWorld станет доступен в консольном проекте.
4. Сборка и запуск
Соберём проект и запустим его. В консоли появится вывод из сгенерированного метода:
В след. посте про чтение кода из файла😌
👉 @KodBlog
Генераторы исходного кода, появившиеся в C# 9, стали мощным инструментом метапрограммирования в .NET. Они позволяют разработчикам автоматически генерировать дополнительный C#-код. Генераторы выполняются на этапе компиляции: анализируют существующий код и создают новые файлы исходников. Эти файлы затем компилируются вместе с остальной кодовой базой, что даёт возможность динамически расширять проект на основе уже написанного кода.
Генераторы исходного кода можно использовать для разных задач: скафолдинга, валидации, повышения читаемости и упрощения поддержки кода.
В C# 12 генераторы получили дальнейшее развитие. Появилась поддержка более сложных сценариев и выросла производительность за счёт уменьшения шаблонного кода и более эффективных проверок на этапе компиляции.
1. Инкрементные генераторы
Инкрементные генераторы регенерируют код только тогда, когда это действительно нужно. Они кэшируют результаты предыдущих запусков и пересобирают сгенерированный код только при изменении входных данных, что заметно ускоряет сборку.
2. Анализ зависимостей исходного кода
Компилятор точнее определяет, какие части проекта зависят от сгенерированного кода. В результате сборки становятся эффективнее, а количество лишних перекомпиляций сокращается.
3. Улучшенная диагностика
Ошибки и предупреждения можно более точно сообщать прямо через сгенерированный код. Разработчик получает понятную обратную связь во время разработки, что уп shows упрощает отладку и сопровождение генераторов.
4. Улучшения API Roslyn
API Roslyn получил дополнительные возможности для анализа и модификации синтаксического дерева, что позволяет реализовывать более сложную логику генерации кода.
Простой пример генератора кода
1. Настройка проекта
Создадим проект библиотеки и установим необходимые NuGet-пакеты:
dotnet new classlib -n MySourceGenerator
cd MySourceGenerator
dotnet add package Microsoft.CodeAnalysis.CSharp
2. Генератор кода
Нам понадобится класс, реализующий интерфейс IIncrementalGenerator:
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
[Generator]
public class HelloWorldGenerator : IIncrementalGenerator
{
public void Initialize(
IncrementalGeneratorInitializationContext context)
{
var src =
"""
using System;
namespace HelloGenerated;
public static class HelloWorld
{
public static void SayHello()
=> Console.WriteLine("Hello from the generated code!");
}
""";
context.RegisterPostInitializationOutput(
ctx => ctx.AddSource(
"HelloWorldGenerated",
SourceText.From(src, Encoding.UTF8)));
}
}
Этот генератор добавляет в проект класс HelloWorld с методом SayHello. Сгенерированный код компилируется вместе с основным проектом, поэтому метод HelloGenerated.HelloWorld.SayHello() можно вызывать напрямую из вашего кода.
3. Интеграция с проектом
Создадим консольное приложение:
dotnet new console -n UseSourceGenerator
Добавим ссылку на проект генератора:
<ProjectReference
Include="..\MySourceGenerator\MySourceGenerator.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
Обратите внимание на два нестандартных атрибута:
OutputItemType="Analyzer" — указывает, что проект должен использоваться как анализатор;
ReferenceOutputAssembly="false" — гарантирует, что целевой проект не будет ссылаться на DLL генератора во время компиляции.
После этого сгенерированный класс HelloWorld станет доступен в консольном проекте.
4. Сборка и запуск
Соберём проект и запустим его. В консоли появится вывод из сгенерированного метода:
// Program.cs
HelloGenerated.HelloWorld.SayHello();
В след. посте про чтение кода из файла
Please open Telegram to view this post
VIEW IN TELEGRAM
❤7👍1🤔1
This media is not supported in your browser
VIEW IN TELEGRAM
Это рай для программистов: репозиторий с сотнями бесплатных API.
Отлично подходит для практики и прокачки навыков программирования
Забираем здесь🥶
👉 @KodBlog
Отлично подходит для практики и прокачки навыков программирования
Забираем здесь
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
Знаешь, что в .NET 10 сильно снизили накладные расходы при работе с любыми встроенными коллекциями через интерфейсы (да, не только с массивами)?
Итерация по списку через IList<T> или по словарю через IDictionary<K, V> и т.п. теперь почти не даёт абстрактного оверхеда.
👉 @KodBlog
Итерация по списку через IList<T> или по словарю через IDictionary<K, V> и т.п. теперь почти не даёт абстрактного оверхеда.
Please open Telegram to view this post
VIEW IN TELEGRAM
🤯11👍10🔥4❤2
Держи инструмент для запросов к данным, если уже надоели классические клиенты для БД: agx
Внутри есть умный SQL-редактор с подсветкой синтаксиса и помощью LLM при написании запросов, браузер схем для быстрого просмотра структуры данных, а также импорт данных из файлов через drag-and-drop.
Работает как нативное десктопное приложение и при этом поддерживает подключение к удалённым серверам через веб-интерфейс, что удобно под разные сценарии использования.
🛌
👉 @KodBlog
Внутри есть умный SQL-редактор с подсветкой синтаксиса и помощью LLM при написании запросов, браузер схем для быстрого просмотра структуры данных, а также импорт данных из файлов через drag-and-drop.
Работает как нативное десктопное приложение и при этом поддерживает подключение к удалённым серверам через веб-интерфейс, что удобно под разные сценарии использования.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
Разбираем Server-Sent Events в ASP.NET Core и .NET 10.
Обновления UI в реальном времени больше не считаются просто желательной фичей. Большинство современных приложений ожидают поток данных в реальном времени от сервера. Долгие годы основным решением в экосистеме .NET был SignalR. Он действительно очень мощный, но для более простых сценариев приятно иметь альтернативы.
В ASP.NET Core 10 появился собственный высокоуровневый API для Server-Sent Events (SSE). Он закрывает разрыв между примитивным HTTP-поллингом и полнодуплексными WebSockets через SignalR.
Зачем?
SignalR — инструмент, который автоматически работает с WebSockets, Long Polling и SSE, предоставляя полнодуплексный (двусторонний) канал связи. Но за это приходится платить: свой протокол, обязательная клиентская библиотека и необходимость в «липких сессиях» или отдельном бэкенде (например, Redis) для масштабирования.
В отличие от SignalR, SSE:
- Однонаправленные - разработаны специально для потоковой передачи данных с сервера на клиент.
- Нативны для HTTP - это стандартный HTTP-запрос с типом содержимого text/event-stream. Никаких пользовательских протоколов.
- Поддерживают автоматическое переподключение - браузеры обрабатывают переподключения напрямую через API EventSource.
- Легковесны - нет тяжёлых клиентских библиотек или сложной логики рукопожатия.
Простейшая SSE-конечная точка
Мы можем использовать новый объект Results.ServerSentEvents, чтобы вернуть поток событий из любого IAsyncEnumerable<T>. Поскольку IAsyncEnumerable представляет поток данных, который может приходить со временем, сервер понимает, что HTTP-соединение нужно держать открытым, а не закрывать его после первого куска данных.
Минимальный пример SSE-эндпоинта, который в реальном времени передаёт события о размещении заказов:
Когда клиент обращается к этой конечной точке:
Сервер отправляет заголовок Content-Type: text/event-stream.
Соединение остаётся открытым и ждёт данные.
Как только приложение пишет новый заказ в канал, IAsyncEnumerable возвращает элемент, и .NET сразу отправляет его в браузер по открытому HTTP-соединению.
Это очень эффективный способ реализовать push-уведомления без накладных расходов, характерных для stateful-протоколов.
В примере используется Channel. В реальном приложении это может быть фоновый сервис, который слушает очередь сообщений (например, RabbitMQ или Azure Service Bus) или поток изменений БД и прокидывает новые события в канал для доставки подключённым клиентам.
В след. посте поговорим про обработку пропущенных событий👍
👉 @KodBlog
Обновления UI в реальном времени больше не считаются просто желательной фичей. Большинство современных приложений ожидают поток данных в реальном времени от сервера. Долгие годы основным решением в экосистеме .NET был SignalR. Он действительно очень мощный, но для более простых сценариев приятно иметь альтернативы.
В ASP.NET Core 10 появился собственный высокоуровневый API для Server-Sent Events (SSE). Он закрывает разрыв между примитивным HTTP-поллингом и полнодуплексными WebSockets через SignalR.
Зачем?
SignalR — инструмент, который автоматически работает с WebSockets, Long Polling и SSE, предоставляя полнодуплексный (двусторонний) канал связи. Но за это приходится платить: свой протокол, обязательная клиентская библиотека и необходимость в «липких сессиях» или отдельном бэкенде (например, Redis) для масштабирования.
В отличие от SignalR, SSE:
- Однонаправленные - разработаны специально для потоковой передачи данных с сервера на клиент.
- Нативны для HTTP - это стандартный HTTP-запрос с типом содержимого text/event-stream. Никаких пользовательских протоколов.
- Поддерживают автоматическое переподключение - браузеры обрабатывают переподключения напрямую через API EventSource.
- Легковесны - нет тяжёлых клиентских библиотек или сложной логики рукопожатия.
Простейшая SSE-конечная точка
Мы можем использовать новый объект Results.ServerSentEvents, чтобы вернуть поток событий из любого IAsyncEnumerable<T>. Поскольку IAsyncEnumerable представляет поток данных, который может приходить со временем, сервер понимает, что HTTP-соединение нужно держать открытым, а не закрывать его после первого куска данных.
Минимальный пример SSE-эндпоинта, который в реальном времени передаёт события о размещении заказов:
app.MapGet("orders/realtime", (
ChannelReader<OrderPlacement> reader,
CancellationToken ct) =>
{
// ReadAllAsync возвращает IAsyncEnumerable
// Results.ServerSentEvents заставляет браузер держать соединение открытым
// Новые данные отправляются клиенту по мере поступления из канала
return Results.ServerSentEvents(
reader.ReadAllAsync(ct),
eventType: "orders");
});Когда клиент обращается к этой конечной точке:
Сервер отправляет заголовок Content-Type: text/event-stream.
Соединение остаётся открытым и ждёт данные.
Как только приложение пишет новый заказ в канал, IAsyncEnumerable возвращает элемент, и .NET сразу отправляет его в браузер по открытому HTTP-соединению.
Это очень эффективный способ реализовать push-уведомления без накладных расходов, характерных для stateful-протоколов.
В примере используется Channel. В реальном приложении это может быть фоновый сервис, который слушает очередь сообщений (например, RabbitMQ или Azure Service Bus) или поток изменений БД и прокидывает новые события в канал для доставки подключённым клиентам.
В след. посте поговорим про обработку пропущенных событий
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥14👍6👏2❤1
Выбирай направление:
Промпты, обучение, шпаргалки и полезные ресурсы на каждую тему!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2👎1🥴1