23284
Все самое полезное для C#-разработчика в одном канале. По рекламе: @proglib_adv Учиться у нас: https://proglib.io/w/b60af5a4 Для обратной связи: @proglibrary_feeedback_bot РКН: https://gosuslugi.ru/snet/67a5c81cdc130259d5b7fead
Интеграция LLM в .NET: подходы RAG и Fine-tuning
23 января в 19:00 обсудим создание интеллектуальных ассистентов на открытом уроке к курсу «Разработка ИИ агентов». Разберём, как реализовать контекстный поиск по вашим документам и когда стоит прибегать к дообучению моделей.
Спикер — Игорь Стурейко, тимлид в «Газпроме» и эксперт с 20-летним опытом в ML. Игорь подготовил видеосообщение, в котором рассказывает о переходе от чат-ботов к автономным агентам и архитектуре будущей программы.
Ключевые темы:
— использование RAG для ответов по внутренней документации;
— фреймворки уровня LangChain и LlamaIndex в Enterprise-среде;
— работа с хранилищами векторных эмбеддингов (FAISS, Chroma).
📅 Когда: 23.01 в 19:00 МСК
Узнать подробности
🧬 Мутационное тестирование
Вы пишете unit-тесты, coverage показывает 80-90%, но уверены ли вы, что эти тесты реально ловят баги? Мутационное тестирование помогает проверить качество самих тестов.
Суть подхода
Инструмент автоматически вносит небольшие изменения в ваш код: меняет операторы, условия, константы. Если тесты проходят с «поломанным» кодом — значит, они недостаточно строгие.
Stryker.NET в действии
Установка занимает пару минут через dotnet tool. После запуска Stryker анализирует код, создаёт мутантов и прогоняет по ним тесты. В отчёте вы видите:
• Какие мутации выжили
• Mutation Score — процент убитых мутантов
• Конкретные строки кода, где тесты слабые
Практический пример
Допустим, у вас метод проверяет возраст пользователя. Stryker меняет age >= 18 на age > 18. Если тест с граничным значением 18 отсутствует — мутант выживает, и вы понимаете, где дописать проверку.
Stryker умеет работать с xUnit, NUnit, MSTest. Настраивается через JSON-файл, где можно указать пороговые значения mutation score, исключить файлы из анализа, настроить уровни логирования.
Процесс требует времени — каждая мутация прогоняет весь набор тестов. Но результат того стоит: вы находите слепые зоны в тестовом покрытии, которые обычный coverage не показывает.
➡️ Репозиторий
📍 Навигация: Вакансии • Задачи • Собесы
🐸Библиотека шарписта
#il_люминатор
👍 JIT .NET 10 убирает абстракции почти бесплатно
В .NET 10 Microsoft сильно доработали JIT компилятор. Цель простая сделать так, чтобы привычный высокоуровневый С# код работал почти как вручную вылизанный низкоуровневый.
Главная идея обновления JIT убрать лишнюю цену за высокоуровневые конструкции. Раньше интерфейсы, делегаты, итераторы легко приносили дополнительные аллокации и лишнюю индирекцию, сейчас JIT старается пробиться сквозь них к конкретным типам и генерировать более плотный машинный код.
Ключевой механизм это улучшенный escape analysis. Если JIT может доказать, что объект живет только внутри метода не передается наружу и не сохраняется в поля, он размещает его на стеке.
Пример:
[Benchmark]
[Arguments(42)]
public int SumWithOffset(int offset)
{
Func<int, int> addOffset = value => value + offset;
return ApplyTwice(addOffset, offset);
}
🚀 Одна строка кода для ускорения EF Core
Когда EF Core загружает связанные сущности через Include(), он генерирует один огромный SQL-запрос с множеством JOIN. Результат? Тысячи дублирующихся строк, гигабайты трафика и медленная работа.
Пример:
var posts = await context.Posts
.Include(p => p.Comments)
.ThenInclude(c => c.Reactions)
.ToListAsync();
.AsSplitQuery() — и EF Core разобьёт один большой запрос на несколько маленьких:var posts = await context.Posts
.AsSplitQuery() // ← магия здесь
.Include(p => p.Comments)
.ThenInclude(c => c.Reactions)
.ToListAsync();
📍 Навигация: Вакансии • Задачи • Собесы
🐸Библиотека шарписта
#garbage_collector
🧑💻 Исключения — не для обычного управления потоком
Исключения во флоу — это не просто дорогие операции. Они дороги когнитивно: затуманивают логику программы и размывают границу между ошибкой и нормальным поведением.
Антипаттерн, который всё ещё встречается:
{
var value = dictionary[key];
Process(value);
}
catch (KeyNotFoundException)
{
// ignore
}if (dictionary.TryGetValue(key, out var value))
{
Process(value);
}
👨💻 Когда if начинает распухать
Паттерн стратегия подходит, когда один и тот же сценарий можно выполнить несколькими способами, и выбор зависит от условий. Вместо большого switch внутри сервиса разные варианты выносятся в отдельные классы с общим интерфейсом, а контекст просто делегирует работу выбранной реализации.
Типовая схема такая. Есть интерфейс Strategy, есть несколько конкретных стратегий, и есть контекст, который держит ссылку на стратегию и вызывает ее метод, не зная деталей реализации. Это снижает связность и позволяет добавлять новые варианты без переписывания старого кода.
Мини пример на C#:
public interface IDiscountStrategy
{
decimal Apply(decimal total);
}
public sealed class RegularDiscount : IDiscountStrategy
{
public decimal Apply(decimal total) => total;
}
public sealed class VipDiscount : IDiscountStrategy
{
public decimal Apply(decimal total) => total * 0.9m;
}
public sealed class Checkout
{
private readonly IDiscountStrategy _discount;
public Checkout(IDiscountStrategy discount) => _discount = discount;
public decimal TotalWithDiscount(decimal total) => _discount.Apply(total);
}
var checkout = serviceProvider.GetRequiredService<Checkout>();
var total = checkout.TotalWithDiscount(100m);
📎 SelectMany это двойной foreach, просто в красивой упаковке
SelectMany проектирует каждый элемент во вложенную последовательность и потом склеивает эти последовательности в одну общую.
И это отличный выбор для чистой проекции без сложной логики, когда нужно ровно развернуть коллекцию коллекций.
Но если в задаче появляется управление потоком или условия выхода:
var allItems = orders
.SelectMany(o => o.Items)
.Where(i => i.IsActive)
.ToList();
var allItems = new List<Item>();
foreach (var order in orders)
{
foreach (var item in order.Items)
{
if (!item.IsActive)
continue;
allItems.Add(item);
}
}
break, continue, счетчики, ограничения, и не гадать как они сложатся с отложенным выполнением.
🏭 Новая база корпоративной культуры
В 2025 году индустрия провела эксперимент и пришла к циничному выводу: забота о сотрудниках экономически невыгодна. Теперь карьерный рост зависит не от крутого продукта, а от способности закрывать KPI и не сгореть раньше коллег.
Почему так произошло и кто в этом виноват — разбираемся в статье.
📍 Навигация: Вакансии • Задачи • Собесы
🐸Библиотека шарписта
🌐 Браузер без всего лишнего
Наткнулись на проект, который помогает вырезать из Chrome, Edge и Firefox всё лишнее, что не относится к работе браузера.
Что сносит:
• AI-фичи (Copilot в Edge, AI-саммари в Chrome)
• Телеметрия и отчёты об использовании
• Спонсорский контент и реклама
• Интеграция сторонних продуктов
• Рекомендованные новости и виджеты
• Бесполезные UI-элементы
Вместо ручного копания в about:config или реестре Windows — готовые конфиги + скрипты установки.
➡️ Удалить мусор из браузера
📍 Навигация: Вакансии • Задачи • Собесы
🐸Библиотека шарписта
#async_news
Roadmap для .NET-разработчика: от промптов к Semantic Kernel
ИИ-агенты становятся стандартом автоматизации. Для тех, кто работает в экосистеме Microsoft, это шанс занять нишу архитектора автономных систем.
Ваш план развития:
— понимание основ агентной логики и Reasoning;
— проектирование систем памяти и доступа к данным;
— создание мультиагентных пайплайнов для рабочих задач;
— интеграция агентов в бизнес-процессы компании.
Курс «Разработка ИИ-агентов» — это интенсивный путь от теории до создания сложных производственных систем.
Начать погружение в ИИ
До 19 января купите курс и заберите два дополнительных в подарок (акция «3 в 1»).
🖨 Добавляем картинку в PDF
Разбираемся, как встроить изображение в PDF так, чтобы всё корректно печаталось, с помощью C#.
IronPdf — это библиотека, которая работает по принципу «что видите в HTML, то получите в PDF». Вместо возни с координатами вы просто верстаете страницу как для браузера.
Библиотека платная для коммерческих проектов, но есть пробный период. Для некоммерческих целей можно использовать бесплатно.
Самый простой способ — сгенерировать HTML с тегом <img> и конвертировать в PDF:
using IronPdf;
var renderer = new ChromePdfRenderer();
string html = @"
<html>
<body>
<img src='logo.png' style='width: 200px;' />
<p>Текст под картинкой</p>
</body>
</html>";
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("document_with_image.pdf");
byte[] imageBytes = File.ReadAllBytes("logo.png");
string base64 = Convert.ToBase64String(imageBytes);
string html = $@"
<html>
<body>
<img src='data:image/png;base64,{base64}' style='width: 200px;' />
<h2>Отчёт за декабрь</h2>
</body>
</html>";
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("report.pdf");var existingPdf = PdfDocument.FromFile("contract.pdf");
// Создаём HTML со штампом
string stampHtml = @"
<div style='position: absolute; bottom: 50px; right: 50px;'>
<img src='approved_stamp.png' style='width: 100px; opacity: 0.7;' />
</div>";
var stampPdf = renderer.RenderHtmlAsPdf(stampHtml);
// Накладываем на каждую страницу
foreach (var page in existingPdf.Pages)
{
page.AddBackgroundPdf(stampPdf);
}
existingPdf.SaveAs("contract_approved.pdf");
📎 Когда хочется коротко, но выходит долгоGroupBy в LINQ удобный и выразительный, поэтому его часто тянут в любой код, где надо что то посчитать по ключу.
Проблема в том, что GroupBy решает задачу группировки, а агрегация это частный случай, и за него иногда приходится платить лишним.
Типичный паттерн выглядит так:
var totals = orders
.GroupBy(o => o.CustomerId)
.Select(g => new
{
CustomerId = g.Key,
Total = g.Sum(o => o.Amount)
})
.ToList();
GroupBy внутри строит структуру групп, а значит элементы буферизуются и создаются объекты группировок, даже если в итоге нужен только итоговый Total.var totals = new Dictionary<int, decimal>();
foreach (var order in orders)
{
if (totals.TryGetValue(order.CustomerId, out var current))
totals[order.CustomerId] = current + order.Amount;
else
totals[order.CustomerId] = order.Amount;
}
😁 Было?
👍 — Да
😁 — Нет
📍 Навигация: Вакансии • Задачи • Собесы
🐸Библиотека шарписта
#garbage_collector
😊 Прощаемся с 2025
Каникулы отработали свое, а вместе с ними осталась подборка самых сильных материалов за прошлый год.
Админ врывается в 2026 и вас зовёт за собой. Расскажите чем занимались на праздниках👇
📍 Навигация: Вакансии • Задачи • Собесы
🐸Библиотека шарписта
🔄 NBomber 6.2.0: фильтрация метрик, OpenTelemetry и улучшенные отчёты
NBomber — это фреймворк для распределённого нагрузочного тестирования в .NET. Вы пишете сценарии на обычном C# или F#, тестируете любые системы независимо от протокола и модели взаимодействия.
Что нового в версии 6.2.0:
Теперь можно фильтровать метрики по имени — полезно, когда у вас десятки показателей. На графике Throughput отображается Fail RPS, так что проблемы видны сразу.
Кластерный режим теперь настраивается через CLI без JSON-конфига. Можно указать целевые сценарии, а группы агентов стали опциональными.
Реализована интеграция с OpenTelemetry — теперь метрики можно отправлять в стандартные системы мониторинга.
➡️ Релиз на GitHub
📍 Навигация: Вакансии • Задачи • Собесы
🐸Библиотека шарписта
#async_news
📎 Преобразование Excel в JSON
Сделаем минимальный код для чтения таблиц для конвертации в JSON.
Подключите NuGet-пакеты FreeSpire.XLS и Newtonsoft.Json. Первая читает .xlsx без зависимостей, вторая формирует JSON.
Код начинается с загрузки файла в поток. Библиотека преобразует лист в DataSet, где строки становятся объектами. Первая строка служит заголовками полей.
Чтение и конвертация данных:
using Spire.Xls;
using Newtonsoft.Json;
Workbook wb = new Workbook();
wb.LoadFromFile("данные.xlsx");
DataTable таблица = wb.Worksheets.ExportDataTable();
JsonSerializerSettings опции = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore
};
string результат = JsonConvert.SerializeObject(таблица, опции);
File.WriteAllText("выход.json", результат);
🛠 Parallel.For — когда циклы работают параллельно
Представьте: у вас есть массив из миллиона элементов, и каждый нужно обработать. Обычный цикл for будет делать это последовательно — один элемент за другим. А что если задействовать все ядра процессора?
Базовое использование:
// Было
for (int i = 0; i < 1000; i++)
{
ProcessImage(images[i]);
}
// Стало
Parallel.For(0, 1000, i =>
{
ProcessImage(images[i]);
});
public const Price = OldPrice; // до 20.01
Мы меняем значение константы: завтра обучение станет дороже. Успейте пробросить заказ сегодня, чтобы зафиксировать текущую стоимость в своём стеке.
Инициализировать рост компетенций
👼 Дайджест первой недели
Прошла первая рабочая неделя 2026 года. Собираем яркие материалы и новости в один пост.
— Microsoft показали как делать виджеты
— Удаляем хлам из браузера
— Новая база корпоративной культуры
— Январские обновления .NET
— Скоро будет домен .meow
📍 Навигация: Вакансии • Задачи • Собесы
🐸Библиотека шарписта
#async_news
🔄 .NET: январские обновления
Microsoft выпустила очередную порцию сервисных обновлений для платформы .NET. Вышли новые версии для трёх поддерживаемых веток:
.NET 10.0.2 — самая свежая версия платформы
.NET 9.0.12 — актуальная STS-версия
.NET 8.0.23 — предыдущая LTS-версия
➡️ Блог разработчиков
📍 Навигация: Вакансии • Задачи • Собесы
🐸Библиотека шарписта
#async_news
«Этот манёвр будет стоить нам 51 год...»
Для .NET разработчика архитектура и алгоритмы — это база. Если вы хотите проектировать сложные системы или внедрять ИИ в свои энтерпрайз-решения, сейчас лучший момент для старта.
В Proglib Academy поднимаются цены. Успейте забрать курс по старой стоимости:
— Разработка ИИ-агентов
— Математика для разработки AI-моделей
— ML для старта в Data Science
— Математика для Data Science
— Специалист по ИИ
— Алгоритмы и структуры данных
— Программирование на Python
— Основы IT для непрограммистов
— Архитектуры и шаблоны проектирования
Забрать по старой цене
⚠️ Повышение уже 19 января
🤩 Громкость прокруткой колеса
В дефолтном микшере Windows 11 для настройки громкости отдельных приложений нужно сделать много действий:
1. В меню найти иконку громкости и кликнуть по ней
2. Кликнуть по ещё одной иконке в выпавшем меню
3. Пролистать вниз и настроить громкость.
Мод с Windhawk меняет это: наводим на иконку звука в таскбаре — прокручиваем колёсико мыши — сразу меняем громкость активного приложения.
📍 Навигация: Вакансии • Задачи • Собесы
🐸Библиотека шарписта
#async_news
⚡️ Чистый LINQ, грязные аллокации
Проблема LINQ в горячих местах обычно не в вычислениях, а в том, что вокруг каждого запроса появляются маленькие кусочки инфраструктуры.Where и Select сами по себе ленивые, они не выполняются пока не началось перечисление. Но как только появляется ToArray, начинается реальная работа и выделяется новый массив под результат
Если внутри цикла постоянно вызывается ToArray, то постоянно создаются новые массивы.
Если еще и лямбды захватывают переменные, добавляются лишние замыкания, и это тоже может стать частью мусора, который потом будет собирать GC.
Пример:
foreach (var batch in batches)
{
var validItems = batch
.Where(IsValid)
.Select(Transform)
.ToArray();
Process(validItems);
}
var buffer = new List<Result>(batch.Count);
foreach (var item in batch)
{
if (!IsValid(item))
continue;
buffer.Add(Transform(item));
}
Process(CollectionsMarshal.AsSpan(buffer));
buffer.Clear();
CollectionsMarshal.AsSpan дает доступ к внутреннему массиву List как Span, то есть можно передать данные дальше без копирования, но важно не менять List пока Span используется.
📍 Навигация: Вакансии • Задачи • Собесы
🐸Библиотека шарписта
#garbage_collector
👨💻 Как превратить O(1) в O(n)
Одна из самых неприятных просадок производительности в C# начинается не с LINQ операторов, а с того, что коллекции начинают жить как IEnumerable.
Пример который встречается постоянно:
IEnumerable<Order> orders = GetOrders();
if (orders.Count() > 0)
{
// do something
}
List или массив, то количество элементов доступно быстро через свойство Count или Length, то есть без прохода по данным.Count() как LINQ метод обязан работать для любого IEnumerable, поэтому в общем случае он перечисляет элементы, пока не посчитает все.if (orders is ICollection<Order> collection)
{
if (collection.Count > 0)
{
// fast path
}
}
else if (orders.Any())
{
// fallback
}
IEnumerable и где после этого зовется Count().
💻 Windows 8 в Linux
Win8DE воссоздает интерфейс Windows 8 для Wayland. Идеально, если есть ностальгия по fluid-анимациям, но без возврата к устаревшей ОС.
Утилита для обоев, экран блокировки, меню запуска, OSD для громкости и яркости, настройки обоев. Без бесполезного шарм-меню.
➡️ Вернуть Windows 8
📍 Навигация: Вакансии • Задачи • Собесы
🐸Библиотека шарписта
#async_news
📱 Виджеты для Android в .NET MAUI
Microsoft показала, как делать виджеты для Android-приложений на .NET MAUI. Разбираемся, что к чему.
Виджет — это кусочек вашего приложения, который живёт на домашнем экране телефона. Пользователь видит нужную информацию сразу, без запуска приложения.
Раньше для виджетов приходилось лезть в Java или Kotlin. Теперь можно на C#.
Как устроено
Виджет работает отдельно от приложения. Даже когда приложение закрыто, виджет продолжает показывать данные и реагировать на действия.
Три основных части:AppWidgetProvider — следит за событиями виджета (создали, обновили, удалили)RemoteViews — рисует интерфейс виджетаBroadcastReceiver — ловит клики и другие действия пользователя
Вы создаёте класс на C#, прописываете его в манифесте Android, рисуете layout в XML. Стандартная Android-разработка, просто на C#.
➡️ Блог Microsoft
📍 Навигация: Вакансии • Задачи • Собесы
🐸Библиотека шарписта
#il_люминатор
👀 Сортировать все ради одного значения — это дорогая привычка
В продакшене до сих пор встречается LINQ паттерн, где коллекцию сначала сортируют, а потом берут первый элемент:
var youngest = people
.OrderBy(p => p.Age)
.First();
var youngest = people.MinBy(p => p.Age);
MinBy возвращает элемент с минимальным ключом и не требует полной сортировки последовательности. Если в коде встречается OrderBy().First() или OrderByDescending().First(), это повод остановиться и проверить, не ищется ли просто минимум или максимум.
✏️ Не копируйте gitignore, генерируйте
Мы раньше кидали эту команду. dotnet new gitignore делает все за вас. В ней уже прописаны все типичные исключения: каталоги bin/, obj/, кэш NuGet, файлы публикаций, временные артефакты IDE и прочие служебные данные, которые не должны попадать в репозиторий.
Не нужно искать шаблон на GitHub или копировать его вручную — всё доступно из коробки.
📍 Навигация: Вакансии • Задачи • Собесы
🐸Библиотека шарписта
#лучшее_из_библиотеки_2025