23284
Все самое полезное для C#-разработчика в одном канале. По рекламе: @proglib_adv Учиться у нас: https://proglib.io/w/b60af5a4 Для обратной связи: @proglibrary_feeedback_bot РКН: https://gosuslugi.ru/snet/67a5c81cdc130259d5b7fead
📎 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 и вас зовёт за собой. Расскажите чем занимались на праздниках👇
📍 Навигация: Вакансии • Задачи • Собесы
🐸Библиотека шарписта
⚙️ Передаём данные между потоками
В C# есть несколько способов передать данные из одного потока в другой. Выбор зависит от требований к производительности, удобству и безопасности. Несколько распространенных вариантов:
1️⃣ Использование BlockingCollection<T>
Это потокобезопасная коллекция, позволяющая передавать данные от одного потока к другому.
var collection = new BlockingCollection<int>();
// Поток-поставщик (Producer)
Task.Run(() =>
{
for (int i = 0; i < 10; i++)
{
collection.Add(i);
Console.WriteLine($"Производитель: добавил {i}");
Thread.Sleep(500);
}
collection.CompleteAdding();
});
// Поток-потребитель (Consumer)
Task.Run(() =>
{
foreach (var item in collection.GetConsumingEnumerable())
{
Console.WriteLine($"Потребитель: получил {item}");
}
}).Wait();
var tcs = new TaskCompletionSource<int>();
// Поток-поставщик
Task.Run(() =>
{
Thread.Sleep(2000);
tcs.SetResult(42);
});
// Поток-потребитель
int result = await tcs.Task;
Console.WriteLine($"Получено: {result}");
var queue = new ConcurrentQueue<int>();
// Поток-поставщик
Task.Run(() =>
{
for (int i = 0; i < 10; i++)
{
queue.Enqueue(i);
Console.WriteLine($"Добавлено {i}");
Thread.Sleep(500);
}
});
// Поток-потребитель
Task.Run(() =>
{
while (true)
{
if (queue.TryDequeue(out int item))
{
Console.WriteLine($"Получено {item}");
}
Thread.Sleep(100);
}
}).Wait();
var channel = Channel.CreateUnbounded<int>();
// Поток-поставщик
_ = Task.Run(async () =>
{
for (int i = 0; i < 10; i++)
{
await channel.Writer.WriteAsync(i);
Console.WriteLine($"Производитель: {i}");
await Task.Delay(500);
}
channel.Writer.Complete();
});
// Поток-потребитель
await foreach (var item in channel.Reader.ReadAllAsync())
{
Console.WriteLine($"Потребитель: {item}");
}
🛠 RabbitMQ плюс MassTransit
Мы уже давали мини гайд по RabbitMQ и MassTransit, и сейчас повторяем его как шпаргалку. Это один из самых прямых способов завести асинхронное общение сервисов в .NET без ручной возни с протоколом и очередями.
Какие инструменты нужны
• RabbitMQ. Брокер сообщений, который позволяет сервисам отправлять и получать сообщения асинхронно, используя очереди.
• MassTransit. Библиотека для .NET, которая предоставляет простой и удобный API для работы с брокерами сообщений.
Как использовать их вместе
1️⃣ Устанавливаем RabbitMQ
RabbitMQ можно развернуть в Docker:
docker run -d --hostname my-rabbit --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:management
builder.Services.AddMassTransit(x =>
{
x.UsingRabbitMq((context, cfg) =>
{
cfg.Host("localhost", "/", h =>
{
h.Username("guest");
h.Password("guest");
});
});
});
public async Task SendMessage(IBus bus, string message)
{
var endpoint = await bus.GetSendEndpoint(new Uri("queue:order-queue"));
await endpoint.Send(new OrderCreated { OrderId = Guid.NewGuid(), Message = message });
}
public class OrderConsumer : IConsumer<OrderCreated>
{
public Task Consume(ConsumeContext<OrderCreated> context)
{
Console.WriteLine($"Получено сообщение: {context.Message.Message}");
return Task.CompletedTask;
}
}
📍 Навигация: Вакансии • Задачи • Собесы
🐸Библиотека шарписта
#лучшее_из_библиотеки_2025
👣 JSON в классы через специальную вставку
Мы уже показывали этот трюк, и сейчас напомним, потому что он спасает часы ручного набора классов. В Visual Studio Edit → Paste Special → Paste JSON As Classes генерирует классы из JSON в буфере, включая атрибуты сериализации.
Пример:
"Colors": [
{
"numberKey": 1,
"isPrimary": true,
"listColors": ["Red", "Blue", "Yellow"]
},
{
"numberKey": 2,
"isPrimary": false,
"listColors": ["Purple", "Green", "Orange"]
}
]
}
public class Root
{
public Color[] Colors { get; set; }
}
public class Color
{
public int NumberKey { get; set; }
public bool IsPrimary { get; set; }
public string[] ListColors { get; set; }
}
⚙️ Scalar в ASP.NET вместо скучного Swagger
Мы уже делились этим материалом весной, и сейчас самое время напомнить. Scalar это интерактивный UI для OpenAPI, который можно подключить к ASP.NET Core и получить более приятную документацию, чем стандартный Swagger UI.
Процесс установки не сложный, а результат превзойдет все ожидания.
➡️ Прочитать статью
📍 Навигация: Вакансии • Задачи • Собесы
🐸Библиотека шарписта
#лучшее_из_библиотеки_2025
C# в 2026-м: от Task.Run к Agent.Run
Зачем просто писать асинхронный код, если можно запустить команду автономных агентов? В новом году Enterprise-разработка переходит на рельсы мультиагентности.
Прокачайте свой стек на курсе по AI-агентам:
— реализуйте паттерн ReAct для принятия решений внутри .NET систем;
— свяжите бизнес-логику с внешними API через n8n;
— освойте протокол MCP для межсистемного взаимодействия агентов;
— внедрите RAG для мгновенной обработки корпоративных данных.
Ваш идеальный дипломный проект: автономная группа агентов, решающая задачи за целый отдел.
❄️ До 12 января действует акция «3 в 1»: курс по ИИ-агентам + 2 курса в подарок.
Стать архитектором агентов
⚡️ DeepSeek + .NET
Снова делимся с вами инструкцией по интеграции DeepSeek в .NET:
1. Создаём новое консольное приложение и устанавливаем необходимые пакеты: инициализируем проект и добавляем библиотеки для работы с HTTP-запросами и конфигурацией JSON.
2. Настраиваем файл appsettings.json: добавляем базовый URL и API-ключ DeepSeek для последующего взаимодействия с API.
3. Определяем модели данных: создаём классы для представления структуры запросов и ответов API.
4. Создаём сервис для взаимодействия с API: реализуем логику отправки сообщений и обработки ответов от DeepSeek.
5. Организуем взаимодействие с пользователем: настраиваем цикл, позволяющий пользователю вводить сообщения, отправлять их в API и получать ответы, обеспечивая непрерывный диалог.
📎 Статья с реализацией
📍 Навигация: Вакансии • Задачи • Собесы
🐸Библиотека шарписта
#лучшее_из_библиотеки_2025
🤩 Громкость прокруткой колеса
В дефолтном микшере 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
👨💻 Жизненный цикл потока в C#
В C# потоки управляются через класс Thread из пространства имен System.Threading. Поток проходит несколько этапов на протяжении своего жизненного цикла. Давайте разберём эти этапы.
— Unstarted. Не запущен
Поток создан, но ещё не запущен. Он находится в этом состоянии сразу после инициализации объекта Thread, но до вызова метода Start().
Thread thread = new Thread(MyMethod);
// Поток создан, но не запущен
Start(). В этом состоянии поток выполняет код, переданный в качестве делегата.thread.Start();
// Поток запущен и выполняется
Thread.Sleep() — поток засыпает на заданное время.Monitor.Wait() или lock — поток ожидает захвата монитора.Thread.Join() — поток ожидает завершения другого потока.Thread.Sleep(1000);
// Поток приостановлен на 1 секунду
lock.Abort(). Поток в этом состоянии больше нельзя запустить снова.thread.Join();
// Основной поток ожидает завершения
IsBackground установлено в true. Фоновые потоки завершаются автоматически, когда завершается основной поток приложения.thread.IsBackground = true;
// Устанавливаем поток как фоновый
Suspend() приостанавливал выполнение потока, но был удалён из новых версий .NET из-за возможных проблем с безопасностью.
⭐️ ValueTask в C#
Мы уже разбирали ValueTask раньше, но сейчас напомним ключевые моменты. ValueTask это структура, которая может хранить либо готовый результат, либо Task, чтобы избежать аллокаций в простых случаях.
Особенности использования ValueTask:
• Повторное ожидание запрещено
Вызывать await несколько раз для одного и того же ValueTask нельзя, так как это может привести к неожиданным результатам:
ValueTask<int> task = GetValueAsync();
int value1 = await task;
int value2 = await task; // Ошибка
TaskValueTask в API, которое ожидает Task, можно вызвать метод .AsTask():Task<int> task = GetValueAsync().AsTask();
ValueTask.
✅ Чек лист по LINQ, который реально помогает
Тогда мы уже выкладывали похожий чек лист, и сейчас вспомним.
Основные принципы LINQ
✓ Понимать разницу между IEnumerable<T> и IQueryable<T>
✓ Фильтровать (Where()) данные как можно раньше в цепочке вызовов.
✓ Извлекать (Select()) только нужные поля, а не всю сущность.
✓ Использовать Any() вместо Count() > 0 для проверки наличия элементов.
✓ Избегать многократных проходов по коллекции (повторных .Where(), .Select())
✓ Знать разницу между отложенным и немедленным выполнением.
Работа с Where, Select, FirstOrDefault
✓ Не использовать Where().FirstOrDefault() – просто FirstOrDefault().
✓ Вызывать Where() перед Select(), а не наоборот.
✓ Использовать FirstOrDefault(predicate), если проверяется только одно значение.
Nullable
✓ Использовать ?? для значений, которые могут быть null.
✓ Использовать DefaultIfEmpty() при GroupBy().
Избегание дублирующих данных
✓ Использовать Distinct() для уникальных значений.
✓ Использовать Union() для объединения без дубликатов.
✓ Использовать Except() и Intersect() для разницы между коллекциями.
Помните, что LINQ — это не просто удобство, а инструмент, требующий понимания его тонкостей.
📍 Навигация: Вакансии • Задачи • Собесы
🐸Библиотека шарписта
#лучшее_из_библиотеки_2025
📍 Навигация: Вакансии • Задачи • Собесы
🐸Библиотека шарписта
#лучшее_из_библиотеки_2025
🔢 Сортировка строк с числами
Мы уже упоминали эту новость из Preview. Сейчас она в .NET 10, и стоит напомнить, что проблема file1, file10, file2 больше не актуальна.
Суть в CompareOptions.NumericOrdering. Issue #13979 висела с 2015, и вот она решена нативно. Флаг заставляет сравнивать цифры как числа, а не посимвольно, так что последовательности вроде версий или имен файлов идут правильно.
Протестить:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
var files = new List<string> { "file10", "file2", "file1", "file20" };
// Обычная сортировка
var sortedLex = files.OrderBy(f => f).ToList();
Console.WriteLine("Лексикографическая: " + string.Join(", ", sortedLex));
// file1, file10, file20, file2
// Числовая сортировка
var comparer = StringComparer.Create(CultureInfo.CurrentCulture, CompareOptions.NumericOrdering);
var sortedNumeric = files.OrderBy(f => f, comparer).ToList();
Console.WriteLine("Числовая: " + string.Join(", ", sortedNumeric));
// file1, file2, file10, file20
📍 Навигация: Вакансии • Задачи • Собесы
🐸Библиотека шарписта
#лучшее_из_библиотеки_2025
🤩 Паттерн-матчинг в C#
Pattern-matching — это функциональность языка, позволяющая проверять объект на соответствие определённому шаблону и выполнять действия, если объект удовлетворяет этим условиям.
Этот механизм помогает писать более читабельный и компактный код, устраняя необходимость в громоздких конструкциях вроде if-else или switch.
Паттерн-матчинг в C# активно развивается с каждой новой версией языка и поддерживает множество типов паттернов, таких как:
1️⃣ Проверка типа
Используется для проверки типа объекта и его преобразования в этом же выражении:
object obj = "Hello, world!";
if (obj is string str)
{
Console.WriteLine($"Длина строки: {str.Length}");
}
int number = 42;
if (number is 42)
{
Console.WriteLine("Число равно 42");
}
int age = 25;
if (age is > 18 and < 30)
{
Console.WriteLine("Возраст в диапазоне от 18 до 30");
}
object shape = new Circle { Radius = 5 };
string description = shape switch
{
Circle { Radius: > 0 } c => $"Круг с радиусом {c.Radius}",
Rectangle { Width: > 0, Height: > 0 } r => $"Прямоугольник {r.Width}x{r.Height}",
_ => "Неизвестная форма"
};Person person = new Person { Name = "Alice", Age = 30 };
if (person is { Name: "Alice", Age: > 25 })
{
Console.WriteLine("Это Алиса старше 25 лет");
}int[] numbers = { 1, 2, 3 };
if (numbers is [1, 2, 3])
{
Console.WriteLine("Массив содержит 1, 2, 3");
}