Начиная с .NET 11 Preview 4 и Visual Studio 18.8, VSTest больше не будет тянуть
Newtonsoft.Json. Вместо него на .NET будет использоваться System.Text.Json, на .NET Framework будет`JSONite`.Почему
Все версии
Newtonsoft.Json ниже 13.0.0 теперь помечены как уязвимые на NuGet.org. Это часть более широкой работы по удалению Newtonsoft.Json из .NET SDK.Что не меняется
Формат сообщений VSTest остаётся прежним. Сериализация идентична независимо от используемой библиотеки. Старые тестовые хосты совместимы с обновлённой платформой и наоборот. Производительность сериализации не ухудшилась.
Кого затронет
Большинство проектов изменений не почувствуют. Проблемы возникнут в трёх случаях.
1. Ошибка сборки — если тестовый проект использует типы
Newtonsoft.Json (JObject, JsonConvert) без явной ссылки на пакет. Раньше пакет «протекал» через VSTest. Теперь нет.Решение — добавить зависимость:
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
2. Ошибка в рантайме — если пакет подключён с
<ExcludeAssets>runtime</ExcludeAssets> и проект рассчитывал на копию из VSTest. Тест упадёт с FileNotFoundException.Решение — убрать
<ExcludeAssets>runtime</ExcludeAssets>.3. Ошибка загрузки адаптера — если тестовый адаптер или data collector использует
Newtonsoft.Json без явной зависимости. Среди известных адаптеров таких случаев пока не обнаружено.Превью-пакеты уже доступны на NuGet как
Microsoft.TestPlatform.* версии 1.0.0-alpha-stj.📍 Навигация: Вакансии • Задачи • Собесы
#async_news
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5❤2😁1😢1
System.Text.Json работает из коробки, но умолчания выбирались для удобства, а не безопасности. Разбираем пять настроек, которые стоит менять перед релизом.• Дублирующиеся ключи
{"role":"user","role":"admin"}По умолчанию десериализатор молча возьмёт
admin. Именно это эксплуатировал CVE-2022-25757: шлюз видел одно значение, бэкенд — другое. Фикс: AllowDuplicateProperties = false.• Неизвестные поля
Поля, которых нет в модели, тихо выбрасываются. Переименовали свойство в DTO и забыли обновить клиент — данные исчезают без ошибки. Фикс:
UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow.•
required компилятора ≠ required для десериализатораC# 11 следит, чтобы объект нельзя было создать без обязательных полей. Десериализатор работает через рефлексию и обходит этот контроль — запишет
null туда, где поля нет в JSON. Баг всплывёт на три слоя глубже в виде NullReferenceException. Фикс: RespectRequiredConstructorParameters = true.• Регистр не важен — по умолчанию
ASP.NET Core включает
PropertyNameCaseInsensitive = true в веб-дефолтах. IsAdmin, isadmin и ISADMIN — одно и то же. Если рядом есть middleware с проверкой строк по сырому JSON — они уже видят разные вещи. Фикс: PropertyNameCaseInsensitive = false.• Nullable-аннотации игнорируются
string Name говорит, что поле не может быть null. Десериализатор это не соблюдает — запишет null без предупреждений. Фикс: RespectNullableAnnotations = true.В .NET 10 всё это одна строка:
var options = JsonSerializerOptions.Strict;
Для ASP.NET Core пайплайна
JsonOptions.SerializerOptions не заменяется целиком, флаги выставляются вручную:builder.Services.Configure<JsonOptions>(o =>
{
var s = o.SerializerOptions;
s.AllowDuplicateProperties = false;
s.UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow;
s.PropertyNameCaseInsensitive = false;
s.RespectNullableAnnotations = true;
s.RespectRequiredConstructorParameters = true;
});
Для прямых вызовов
JsonSerializer.Deserialize вне MVC-пайплайна — передавайте JsonSerializerOptions.Strict напрямую.📍 Навигация: Вакансии • Задачи • Собесы
#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
👍23❤3😁1
🧑💻 SkiaSharp 4.0: движок обновлён, новый сопровождающий, переменные шрифты
Вышел первый превью
Что поменялось в движке
Главное обновление — переход на Skia milestone 147. Это 2,5 года апстрим-изменений, которые достаются автоматически без правки кода.
Качество изображений. Mipmap-шарпенинг включён по умолчанию, уменьшенные изображения стали чётче. Кодеки теперь читают Exif-метаданные и автоматически применяют поворот фото. Большие битмапы, которые не влезают в лимиты текстур GPU, тайлятся автоматически.
Цвет. Поправлены передаточные функции для Rec.709, HLG и PQ. Для тех, кто работает с видео или профессиональной цветокоррекцией, это важно.
Производительность. Незначительный прирост по всем операциям рендеринга. Более заметные улучшения в noise-шейдерах и canvas-операциях.
Безопасность. Обновлены нативные зависимости, включены современные митигации компилятора на всех платформах.
Новые возможности
Переменные шрифты. Полная поддержка OpenType variable fonts через
Палитры цветных шрифтов. Поддержка OpenType CPAL для эмодзи и иконочных шрифтов. Можно переключать палитры или переопределять цвет отдельных глифов.
SKPathBuilder. Новый способ строить пути.
Новые платформы. Добавлены нативные сборки для Linux Bionic и Tizen x64/arm64.
Uno Platform стала сопровождающим
Вместе с релизом объявили, что Uno Platform становится co-maintainer
Для тех, кто зависит от
➡️ Интерактивная галерея с примерами и шейдер-плейграунд.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#async_news
Вышел первый превью
SkiaSharp 4.0. Библиотека существует 10 лет и лежит в основе кроссплатформенной 2D-графики в .NET: её используют .NET MAUI, WebAssembly, WinUI 3. Это первый крупный мейджор за долгое время.Что поменялось в движке
Главное обновление — переход на Skia milestone 147. Это 2,5 года апстрим-изменений, которые достаются автоматически без правки кода.
Качество изображений. Mipmap-шарпенинг включён по умолчанию, уменьшенные изображения стали чётче. Кодеки теперь читают Exif-метаданные и автоматически применяют поворот фото. Большие битмапы, которые не влезают в лимиты текстур GPU, тайлятся автоматически.
Цвет. Поправлены передаточные функции для Rec.709, HLG и PQ. Для тех, кто работает с видео или профессиональной цветокоррекцией, это важно.
Производительность. Незначительный прирост по всем операциям рендеринга. Более заметные улучшения в noise-шейдерах и canvas-операциях.
Безопасность. Обновлены нативные зависимости, включены современные митигации компилятора на всех платформах.
Новые возможности
Переменные шрифты. Полная поддержка OpenType variable fonts через
SkiaSharp и HarfBuzz. Можно получить доступные оси, задать их значения и создавать варианты шрифта по весу, ширине, наклону или кастомным осям.Палитры цветных шрифтов. Поддержка OpenType CPAL для эмодзи и иконочных шрифтов. Можно переключать палитры или переопределять цвет отдельных глифов.
SKPathBuilder. Новый способ строить пути.
SKPath теперь иммутабелен, а SKPathBuilder предоставляет привычный API с MoveTo, LineTo, CubicTo и фабриками фигур. Старые методы SKPath сохранены для обратной совместимости.Новые платформы. Добавлены нативные сборки для Linux Bionic и Tizen x64/arm64.
Uno Platform стала сопровождающим
Вместе с релизом объявили, что Uno Platform становится co-maintainer
SkiaSharp. Они используют библиотеку в собственном рендер-пайплайне и уже сделали значимые вклады: обновления движка Skia, полная реализация API переменных шрифтов, фикс краша с typeface на Android API 36, поддержка генератора биндингов на Linux, интерактивная Wasm-галерея.Для тех, кто зависит от
SkiaSharp в продакшне: библиотека теперь поддерживается двумя организациями, что ускорит обновления и тришаж.📍 Навигация: Вакансии • Задачи • Собесы
#async_news
Please open Telegram to view this post
VIEW IN TELEGRAM
❤7😁1
Чем SelectMany отличается от Select в LINQ
Оба метода принимают функцию-проекцию. Оба работают с коллекциями. Но результат — принципиально разный.
var data = new List<List<int>>
{
new() { 1, 2, 3 },
new() { 4, 5, 6 }
};
var a = data.Select(x => x); // ???
var b = data.SelectMany(x => x); // ???
Подсказка:
Ответ:
Please open Telegram to view this post
VIEW IN TELEGRAM
🥱15😁3❤1🌚1
🛠 yield return под капотом
Многие используют
Что делает компилятор
Вот простой метод:
Компилятор создаёт отдельный класс с полем
Главное свойство — ленивость
Код выполняется только при вызове
Практически
При загрузке данных из БД
Каждый объект создаётся только при переходе к следующей итерации, а не все сразу в памяти.
При каждом вызове метода создаётся новый объект сгенерированного класса в куче. При частых вызовах это влияет на аллокации.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#il_люминатор
Многие используют
yield return, но не задумываются что происходит внутри. Компилятор перестраивает весь метод в конечный автомат.Что делает компилятор
Вот простой метод:
public IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield return 3;
}
Компилятор создаёт отдельный класс с полем
_state. Каждый yield return становится отдельным состоянием в switch:public bool MoveNext()
{
switch (_state)
{
case 0: _state = 1; _current = 1; return true;
case 1: _state = 2; _current = 2; return true;
case 2: _state = 3; _current = 3; return true;
default: return false;
}
}
foreach под капотом вызывает MoveNext() на каждой итерации. Локальные переменные метода становятся полями этого класса — так состояние и сохраняется между вызовами.Главное свойство — ленивость
Код выполняется только при вызове
MoveNext(). Поэтому можно работать с бесконечными последовательностями:public IEnumerable<int> Infinite()
{
int i = 0;
while (true) yield return i++;
}
Infinite().Take(5); // {0, 1, 2, 3, 4} — не зависнет
Практически
При загрузке данных из БД
yield return позволяет начать обработку до того, как все данные загрузятся:public IEnumerable<Order> GetOrders(IDataReader reader)
{
while (reader.Read())
yield return new Order { Id = reader.GetInt32(0) };
}
Каждый объект создаётся только при переходе к следующей итерации, а не все сразу в памяти.
При каждом вызове метода создаётся новый объект сгенерированного класса в куче. При частых вызовах это влияет на аллокации.
async/await работает по той же схеме — это два применения одного паттерна трансформации кода.📍 Навигация: Вакансии • Задачи • Собесы
#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
👍12❤7🔥1
📉 Кандидатов мало, а собесы становятся сложнее
Казалось бы, в кризис найма логично снижать барьеры. Но Яндекс и Сбер делают ровно наоборот: добавляют этапы, запускают ИИ-скрининг, вводят психологические тесты и проверку культурного фита.
Поиск работы даже для сильных специалистов теперь занимает 6–9 месяцев, а воронка найма растёт.
➡️ Разбираем на цифрах и фактах
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
Казалось бы, в кризис найма логично снижать барьеры. Но Яндекс и Сбер делают ровно наоборот: добавляют этапы, запускают ИИ-скрининг, вводят психологические тесты и проверку культурного фита.
Поиск работы даже для сильных специалистов теперь занимает 6–9 месяцев, а воронка найма растёт.
📍 Навигация: Вакансии • Задачи • Собесы
Please open Telegram to view this post
VIEW IN TELEGRAM
🥰5👏1🌚1
sealed в C#: зачем закрывать классыМодификатор
sealed существует в языке давно, но в современных версиях .NET он приобрёл дополнительный вес за счёт оптимизаций JIT-компилятора.Что делает
sealedsealed запрещает наследование. Если класс помечен как sealed, от него нельзя унаследоваться. Можно применять и к отдельным методам с override, тогда этот метод нельзя переопределить дальше по цепочке.public sealed class PaymentConfiguration
{
public string ApiKey { get; set; }
public int TimeoutInSeconds { get; set; }
}
// Ошибка компиляции: нельзя наследоваться от sealed-типа
public class GatewayX : PaymentConfiguration { }
Почему это влияет на производительность
Когда класс открыт для наследования, JIT не может знать наверняка, какой именно метод будет вызван в рантайме. Поэтому он генерирует косвенный вызов через
vtable.; Открытый класс — несколько обращений к памяти перед вызовом
mov eax, [edx]
mov eax, [eax+0x28]
call dword ptr [eax+0x10]
Для
sealed-класса JIT точно знает, какой метод будет вызван. Он убирает обращение к vtable, встраивает тело метода прямо в место вызова и оставляет минимум инструкций:; Sealed-класс — только проверка на null и возврат
cmp [edx], dl
ret
Дополнительно ускоряются операторы
is и as, потому что среде не нужно проходить по всему дереву наследования.Когда
sealed не подойдётEntity Framework создаёт прокси-классы в рантайме для ленивой загрузки. Если сущность
sealed, фреймворк не сможет её расширить:// EF не сможет создать прокси для sealed-класса
public class Order
{
public int Id { get; set; }
public virtual ICollection<OrderItem> Items { get; set; }
}
Аналогичная проблема с библиотеками для мокирования (Moq, NSubstitute). Они создают наследников в рантайме, и
sealed ломает это:public sealed class IntegrationService
{
public bool SendData(string data) => true;
}
// Moq выбросит исключение: нельзя наследоваться от sealed-типа
var mock = new Mock<IntegrationService>();
Архитекторы рекомендуют подход "sealed by default": закрывать все классы сразу, а наследование открывать только когда оно действительно нужно.
Это защищает дизайн от случайных расширений и даёт прирост производительности без дополнительных усилий.
📍 Навигация: Вакансии • Задачи • Собесы
#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
👍23❤4
Кто куда, а админ на шашлыки.
— hh.ru + Госуслуги + трудовая
— VSTest убирает зависимость от Newtonsoft.Json
— Как настроить JsonSerializerOptions
— SkiaSharp 4.0
📍 Навигация: Вакансии • Задачи • Собесы
#async_news
Please open Telegram to view this post
VIEW IN TELEGRAM
🛠 CQRS: что это и зачем оно вам
Мы часто упоминаем CQRS в контексте архитектуры. Пора разобраться, что за ним стоит и когда его стоит применять.
Что такое CQRS
CQRS расшифровывается как Command Query Responsibility Segregation. Суть простая: операции чтения и записи разделяются на два независимых потока.
В классическом подходе один репозиторий или сервис отвечает и за чтение, и за запись. Это удобно, пока система небольшая. Когда нагрузка растёт или модели чтения и записи начинают расходиться, появляются проблемы.
Какую боль решает CQRS
Представьте интернет-магазин. Команда на оформление заказа
Если один и тот же объект обслуживает оба сценария, вы получаете:
• модель данных, которая пытается угодить всем сразу
• сложные запросы с JOIN там, где нужна простая выборка
• трудности с масштабированием — читать нужно в 10 раз чаще, чем писать
CQRS позволяет разделить эти ответственности явно.
Как это выглядит на практике
Допустим, у нас есть приложение на C# с заказами. Для удобства используем
Сначала определяем интерфейсы:
Команда на создание заказа:
Запрос на получение заказов пользователя:
Вызов из контроллера выглядит одинаково для команд и запросов:
Обратите внимание:
Для чтения можно подключить read-replica или отдельную схему, оптимизированную под конкретные запросы. Запись при этом идёт в основное хранилище, а синхронизация происходит через события.
Когда применять
CQRS оправдан, если:
• нагрузка на чтение и запись сильно различается
• модели чтения и записи расходятся, например, вам нужны агрегированные данные на фронте, но нормализованное хранилище на бэке
• вы строите систему с событийной архитектурой или Event Sourcing
CQRS не нужен, если:
• у вас простой CRUD без сложной логики
• команда небольшая и накладные расходы на поддержку двух моделей не оправданы
Если вы чувствуете, что один репозиторий тащит на себе слишком много, это сигнал задуматься о разделении.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#il_люминатор
Мы часто упоминаем CQRS в контексте архитектуры. Пора разобраться, что за ним стоит и когда его стоит применять.
Что такое CQRS
CQRS расшифровывается как Command Query Responsibility Segregation. Суть простая: операции чтения и записи разделяются на два независимых потока.
Query — читает данные, ничего не меняет.Command — меняет состояние, ничего не возвращает (кроме подтверждения).В классическом подходе один репозиторий или сервис отвечает и за чтение, и за запись. Это удобно, пока система небольшая. Когда нагрузка растёт или модели чтения и записи начинают расходиться, появляются проблемы.
Какую боль решает CQRS
Представьте интернет-магазин. Команда на оформление заказа
PlaceOrderCommand обновляет остатки, создаёт запись заказа и запускает цепочку событий. А запрос на страницу каталога GetProductsQuery просто возвращает список товаров с ценами.Если один и тот же объект обслуживает оба сценария, вы получаете:
• модель данных, которая пытается угодить всем сразу
• сложные запросы с JOIN там, где нужна простая выборка
• трудности с масштабированием — читать нужно в 10 раз чаще, чем писать
CQRS позволяет разделить эти ответственности явно.
Как это выглядит на практике
Допустим, у нас есть приложение на C# с заказами. Для удобства используем
MediatR — библиотеку, которая берёт на себя маршрутизацию команд и запросов.Сначала определяем интерфейсы:
public interface ICommand : IRequest { }
public interface IQuery<TResult> : IRequest<TResult> { }Команда на создание заказа:
public record PlaceOrderCommand(string UserId, string ProductId, int Quantity) : ICommand;
public class PlaceOrderCommandHandler : IRequestHandler<PlaceOrderCommand>
{
private readonly IOrderRepository _repo;
public PlaceOrderCommandHandler(IOrderRepository repo) => _repo = repo;
public async Task Handle(PlaceOrderCommand command, CancellationToken ct)
{
var order = Order.Create(command.UserId, command.ProductId, command.Quantity);
await _repo.SaveAsync(order, ct);
}
}
Запрос на получение заказов пользователя:
public record GetUserOrdersQuery(string UserId) : IQuery<IReadOnlyList<OrderDto>>;
public class GetUserOrdersQueryHandler : IRequestHandler<GetUserOrdersQuery, IReadOnlyList<OrderDto>>
{
private readonly IOrderReadRepository _readRepo;
public GetUserOrdersQueryHandler(IOrderReadRepository readRepo) => _readRepo = readRepo;
public async Task<IReadOnlyList<OrderDto>> Handle(GetUserOrdersQuery query, CancellationToken ct)
=> await _readRepo.GetByUserIdAsync(query.UserId, ct);
}
Вызов из контроллера выглядит одинаково для команд и запросов:
// Команда
await _mediator.Send(new PlaceOrderCommand(userId, productId, quantity));
// Запрос
var orders = await _mediator.Send(new GetUserOrdersQuery(userId));
Обратите внимание:
PlaceOrderCommandHandler работает с доменной моделью Order, а GetUserOrdersQueryHandler возвращает OrderDto — упрощённую структуру специально для отображения. Это ключевой момент.IOrderRepository и IOrderReadRepository — два разных интерфейса. Первый смотрит в основную базу и знает о доменных правилах. Второй может работать с read-replica или отдельной проекцией данных.Для чтения можно подключить read-replica или отдельную схему, оптимизированную под конкретные запросы. Запись при этом идёт в основное хранилище, а синхронизация происходит через события.
Когда применять
CQRS оправдан, если:
• нагрузка на чтение и запись сильно различается
• модели чтения и записи расходятся, например, вам нужны агрегированные данные на фронте, но нормализованное хранилище на бэке
• вы строите систему с событийной архитектурой или Event Sourcing
CQRS не нужен, если:
• у вас простой CRUD без сложной логики
• команда небольшая и накладные расходы на поддержку двух моделей не оправданы
Если вы чувствуете, что один репозиторий тащит на себе слишком много, это сигнал задуматься о разделении.
📍 Навигация: Вакансии • Задачи • Собесы
#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍2😁2
🦾 Почему ваши AI-продукты на базе LLM ломаются (и как это чинить)?
Выкатили ИИ-фичу в прод, а она галлюцинирует, падает или выдает мусор? Приглашаем на открытый вебинар, где разберем реальную боль внедрения LLM-агентов и научимся делать так, чтобы «всё работало».
🗓️ Когда: 14 мая в 19:00 МСК
⏱️ Формат: 60 минут мяса + 30 минут ответов на ваши вопросы
🧑🏻💻 Кто вещает: Эмиль Сатаев — Backend Platform Developer (8+ лет в разработке). Человек, который своими руками внедряет LLM и агентные системы в реальные коммерческие сервисы.
🎁 Главный бонус для онлайна:
Только участникам прямого эфира подарим уникальный промокод на скидку 10.000 ₽ на большой курс AgentOps.
👉 Занять место на вебинаре
Выкатили ИИ-фичу в прод, а она галлюцинирует, падает или выдает мусор? Приглашаем на открытый вебинар, где разберем реальную боль внедрения LLM-агентов и научимся делать так, чтобы «всё работало».
🗓️ Когда: 14 мая в 19:00 МСК
⏱️ Формат: 60 минут мяса + 30 минут ответов на ваши вопросы
🧑🏻💻 Кто вещает: Эмиль Сатаев — Backend Platform Developer (8+ лет в разработке). Человек, который своими руками внедряет LLM и агентные системы в реальные коммерческие сервисы.
🎁 Главный бонус для онлайна:
Только участникам прямого эфира подарим уникальный промокод на скидку 10.000 ₽ на большой курс AgentOps.
👉 Занять место на вебинаре
В C# 14 появился более чистый способ писать методы расширения. Да, тот самый отдельный синтаксис
extension:extension(string text)
{
public int WordCount()
{
return text.Split(' ').Length;
}
}
Меньше шаблонного кода. Не нужно каждый раз создавать статический класс и писать this в параметре. Обязательно к использованию в новых проектах!
📍 Навигация: Вакансии • Задачи • Собесы
#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
👍22😁1🌚1
Microsoft выпустила третью часть серии «Building Blocks for AI» — на этот раз про Microsoft Agent Framework.
Что это и зачем
Обычный чат-бот принимает запрос и возвращает ответ. Агент идёт дальше: он сам решает, какие инструменты использовать, вызывает их, оценивает результат и продолжает работу. Вам не нужно писать «если пользователь спросит о погоде — вызови функцию X». Модель разбирается сама.
Фреймворк строится поверх
IChatClient из Microsoft Extensions for AI (MEAI) и поддерживает Azure OpenAI, OpenAI, GitHub Models, Foundry Local и Ollama.Первый агент
Установка:
dotnet add package Microsoft.Agents.AI
Простейший агент:
AIAgent agent = new AzureOpenAIClient(
new Uri(endpoint),
new DefaultAzureCredential())
.GetChatClient(deploymentName)
.AsAIAgent(
instructions: "You are good at telling jokes.",
name: "Joker");
Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate."));
Метод
.AsAIAgent() это аналог .AsIChatClient(), только оборачивает клиент в агента с поддержкой сессий, инструментов и памяти. Стриминг тоже работает из коробки через RunStreamingAsync.Инструменты
Агенты вызывают функции сами, без явных условий в коде. Главное добавить атрибуты
[Description], чтобы модель понимала, когда и как использовать инструмент:[Description("Get the weather for a given location.")]
static string GetWeather(
[Description("The location to get the weather for.")] string location)
=> $"The weather in {location} is cloudy with a high of 15°C.";
AIAgent agent = ...AsAIAgent(
instructions: "You are a helpful assistant",
tools: [AIFunctionFactory.Create(GetWeather)]);Спрашиваем «What is the weather in Amsterdam?» и агент сам вызывает
GetWeather("Amsterdam") и подставляет результат в ответ.Многоходовые разговоры
Сессия сохраняет историю между вызовами:
AgentSession session = await agent.CreateSessionAsync();
await agent.RunAsync("Tell me a joke about a pirate.", session);
await agent.RunAsync("Now add emojis and tell it as a parrot.", session);
Сессию можно сериализовать и восстановить:
JsonElement state = await agent.SerializeSessionAsync(session);
var restored = await agent.DeserializeSessionAsync(state);
Память между сессиями
AIContextProvider позволяет агенту извлекать и помнить факты о пользователе между сессиями. Провайдер работает в два этапа:StoreAIContextAsync запускается после каждого взаимодействия — здесь агент учится из разговора (например, извлекает имя пользователя).ProvideAIContextAsync запускается до — здесь агент получает накопленный контекст перед ответом.Провайдеры стекуются: один хранит имя, другой — предпочтения, третий — подтягивает документы из VectorData.
Граф-воркфлоу и мульти-агенты
Для сложных задач можно соединять агентов через граф:
WorkflowBuilder связывает исполнителей рёбрами:Писатель-критик — агент пишет, второй проверяет, если не ок — отправляет обратно:
WorkflowBuilder builder = new(writerAgent);
builder
.AddEdge(writerAgent, criticAgent)
.AddEdge(criticAgent, writerAgent, condition: result => !result.IsApproved)
.WithOutputFrom(criticAgent, condition: result => result.IsApproved);
Помимо этого доступны параллельное выполнение (fan-out / fan-in), условная маршрутизация, вложенные воркфлоу и циклы с лимитом итераций.
Human-in-the-loop
Для чувствительных операций: запись в БД, финансовые транзакции, отправка сообщений; агент может запрашивать подтверждение человека перед вызовом инструмента. Механизм встроен через
FunctionApprovalRequestContent и FunctionApprovalResponseContent из MEAI.Теперь Microsoft Agent Framework — это единый SDK с понятными абстракциями поверх уже знакомого
IChatClient.📍 Навигация: Вакансии • Задачи • Собесы
#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11❤7❤🔥2
Когда задача уже запущена и нам остаётся только ждать её завершения, некоторые пишут примерно такой код:
while (!task.IsCompleted)
{
Thread.Sleep(100);
}
Выглядит логично, но на практике это сводит на нет весь смысл асинхронности.
Что происходит под капотом
Поток не освобождается. Он занят проверкой статуса в цикле и не делает ничего полезного. Процессор тратит такты на холостые итерации, хотя эти ресурсы могли бы использоваться другим кодом, в том числе самой ожидаемой операцией.
Это особенно критично в серверных приложениях, где количество потоков ограничено. Заблокированный поток в пуле означает, что следующий запрос будет ждать.
Используйте await:
await task;
Это освобождает поток до завершения задачи. Планировщик вернёт управление в нужный момент без лишних проверок и потраченных ресурсов.
📍 Навигация: Вакансии • Задачи • Собесы
#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
😁22❤2👏2👍1👾1
WithDegreeOfParallelism: как управлять параллелизмом в PLINQПо умолчанию PLINQ сам решает, сколько потоков использовать, опираясь на количество ядер процессора. Это удобно, но не всегда подходит.
Что делает WithDegreeOfParallelism
Метод позволяет вручную задать максимальное число потоков, которые PLINQ будет использовать при обработке данных. Это прямой контроль над тем, сколько ресурсов займёт параллельная операция.
Зачем это нужно
Если задачи тяжёлые по CPU, без ограничений PLINQ может «съесть» все ядра. Остальные части приложения начнут тормозить — особенно заметно в десктопных приложениях, где есть UI-поток.
Пример использования:
var results = items
.AsParallel()
.WithDegreeOfParallelism(4)
.Select(ProcessItem)
.ToList();
Здесь мы явно говорим PLINQ использовать не больше 4 потоков, независимо от того, сколько ядер на машине.
Когда применять
Метод полезен в двух ситуациях:
1. Обработка больших файлов или тяжёлых вычислений, когда нужно оставить ресурсы для UI или других процессов.
2. Когда пул потоков по умолчанию перегружен и это влияет на другие операции в приложении.
Устанавливайте значение исходя из реальной нагрузки. Четыре потока это не универсальный ответ, это отправная точка для тестирования.
📍 Навигация: Вакансии • Задачи • Собесы
#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥6❤2
Многие методы в .NET принимают
CancellationToken как параметр. Это правильная практика: метод должен уметь реагировать на отмену. Но что делать, если в конкретном месте отмена просто не нужна?Что такое CancellationToken.None
CancellationToken.None — это пустой токен. Его свойство CanBeCanceled всегда возвращает false. Отменить его невозможно, и никакие CancellationTokenSource к нему не привязаны.Используется там, где токен нужно передать по сигнатуре, но реальной логики отмены нет:
await DoWorkAsync(CancellationToken.None);
Альтернатива через default
В C# есть эквивалентный способ:
await DoWorkAsync(default);
// или явно
await DoWorkAsync(default(CancellationToken));
default(CancellationToken) и CancellationToken.None равны между собой. Два пустых токена всегда равны, вне зависимости от того, как они созданы.Когда применять
Типичный сценарий: вызов метода в
Main, в тестах или в коде инициализации, где нет смысла настраивать отмену, но метод её принимает.// Тест без отмены
var result = await repository.GetAllAsync(CancellationToken.None);
// Инициализация при старте приложения
await migrationService.RunAsync(CancellationToken.None);
Что не стоит делать
Передавать
new CancellationToken() вместо CancellationToken.None. Технически результат тот же, но это создаёт ложное ощущение, что токен где-то настроен. CancellationToken.None читается однозначно: отмены здесь нет намеренно.📍 Навигация: Вакансии • Задачи • Собесы
#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7❤3👏1
Иногда при работе с PostgreSQL нужно вставить несколько связанных записей в одной транзакции, но внешние ключи проверяются сразу и транзакция падает.
DEFERRABLE INITIALLY DEFERRED решает эту проблему — ограничение откладывается до конца транзакции.Что это и зачем
По умолчанию PostgreSQL проверяет ограничения (
FOREIGN KEY, UNIQUE, CHECK) сразу при выполнении каждого оператора. Если порядок вставки нарушает ссылочную целостность — получаем ошибку, даже если к концу транзакции всё было бы корректно.DEFERRABLE INITIALLY DEFERRED говорит базе: не проверяй ограничение после каждой операции — подожди до COMMIT.Это удобно, когда:
- записи ссылаются друг на друга (циклические FK)
- порядок вставки нельзя гарантировать
- нужно пакетно импортировать данные без временного отключения ограничений
Пример схемы
Допустим, есть две таблицы, которые ссылаются друг на друга:
CREATE TABLE teams (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
lead_id INT REFERENCES employees(id) DEFERRABLE INITIALLY DEFERRED
);
CREATE TABLE employees (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
team_id INT REFERENCES teams(id) DEFERRABLE INITIALLY DEFERRED
);
Без
DEFERRABLE INITIALLY DEFERRED вставить команду со ссылкой на сотрудника, которого ещё нет — невозможно. С ним — всё проверяется один раз при COMMIT.Как использовать в C# с Npgsql
Npgsql поддерживает транзакции напрямую. Главное — убедиться, что обе вставки выполняются в одной транзакции.await using var conn = new NpgsqlConnection(connectionString);
await conn.OpenAsync();
await using var tx = await conn.BeginTransactionAsync();
// Вставляем команду, но сотрудник ещё не существует
await using (var cmd = new NpgsqlCommand(
"INSERT INTO teams (id, name, lead_id) VALUES (1, 'Backend', 42)", conn, tx))
{
await cmd.ExecuteNonQueryAsync();
}
// Теперь вставляем сотрудника
await using (var cmd = new NpgsqlCommand(
"INSERT INTO employees (id, name, team_id) VALUES (42, 'Иван', 1)", conn, tx))
{
await cmd.ExecuteNonQueryAsync();
}
// FK проверяются здесь — оба существуют, всё ок
await tx.CommitAsync();
Без
DEFERRABLE INITIALLY DEFERRED первая вставка упала бы с ошибкой foreign key violation, потому что employees(42) не существует в момент вставки команды.Как использовать с EF Core
EF Core сам по себе не умеет объявлять
DEFERRABLE ограничения через Fluent API. Но можно сделать через HasCheckConstraint с raw SQL или через миграцию.Самый надёжный способ — написать миграцию вручную:
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(@"
ALTER TABLE teams
DROP CONSTRAINT IF EXISTS teams_lead_id_fkey;
ALTER TABLE teams
ADD CONSTRAINT teams_lead_id_fkey
FOREIGN KEY (lead_id) REFERENCES employees(id)
DEFERRABLE INITIALLY DEFERRED;
");
}
После этого транзакции EF Core будут пользоваться отложенной проверкой автоматически — ничего дополнительно настраивать не нужно.
Разница между DEFERRED и IMMEDIATE
DEFERRABLE означает, что ограничение можно отложить. INITIALLY DEFERRED — что оно отложено по умолчанию для каждой транзакции.Если нужно временно изменить поведение внутри конкретной транзакции — можно явно переключить режим:
-- Внутри транзакции включить немедленную проверку
SET CONSTRAINTS teams_lead_id_fkey IMMEDIATE;
-- Или отложить конкретное ограничение
SET CONSTRAINTS teams_lead_id_fkey DEFERRED;
В C# через Npgsql это выполняется как обычный SQL-запрос внутри транзакции:
await using var cmd = new NpgsqlCommand(
"SET CONSTRAINTS ALL DEFERRED", conn, tx);
await cmd.ExecuteNonQueryAsync();
📍 Навигация: Вакансии • Задачи • Собесы
#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🤔1
🎤 Ваши знания по ИИ-агентам + наша аудитория в 1 млн человек = профит
Мы в Proglib активно качаем тему ИИ-агентов. Если вы в теме, то у нас есть предложение 👇
Что с нас?
- Огромный охват: пропиарим ваши соцсети и продукты на 1 000 000+ айтишников.
- Личный бренд: станете узнаваемым экспертом в самой горячей нише 2026 года.
- Никакой рутины: наши редакторы сами упакуют ваши мысли в крутые посты.
Что с вас?
Любой экспертный контент по ИИ-агентам: кейсы из прода, шпаргалки, статьи, наработки по стеку (LangGraph, CrewAI, AutoGen и др.) или просто мысли по архитектуре.
👉 Стать экспертом и заявить о себе
Мы в Proglib активно качаем тему ИИ-агентов. Если вы в теме, то у нас есть предложение 👇
Что с нас?
- Огромный охват: пропиарим ваши соцсети и продукты на 1 000 000+ айтишников.
- Личный бренд: станете узнаваемым экспертом в самой горячей нише 2026 года.
- Никакой рутины: наши редакторы сами упакуют ваши мысли в крутые посты.
Что с вас?
Любой экспертный контент по ИИ-агентам: кейсы из прода, шпаргалки, статьи, наработки по стеку (LangGraph, CrewAI, AutoGen и др.) или просто мысли по архитектуре.
👉 Стать экспертом и заявить о себе
🔥4👍2
.NET Developer (Backend) Middle — до 6,000 $, удалёнка
C#/.NET-разработчик — удалёнка
Разработчик С# (User Interface, WPF) — от 120 000 ₽, офис в Перми
Please open Telegram to view this post
VIEW IN TELEGRAM
AsOrdered — когда порядок элементов важен в PLINQAsParallel() обрабатывает данные параллельно и не гарантирует, что результат выйдет в том же порядке, что и входная последовательность. Если порядок важен, нужен AsOrdered().Что делает AsOrdered
Метод говорит PLINQ сохранять исходный порядок элементов в результирующей коллекции. Параллельная обработка при этом остаётся, но результат будет упорядочен так же, как входные данные.
Зачем это нужно
Есть случаи, когда порядок критичен — например, рендеринг элементов в UI или формирование отчёта, где строки должны идти в определённой последовательности. Без
AsOrdered() результат непредсказуем.Пример использования:
var results = items
.AsParallel()
.AsOrdered()
.Select(ProcessItem)
.ToList();
PLINQ обработает элементы параллельно, но в
results они окажутся в том же порядке, что и в items.Что учитывать
AsOrdered() добавляет накладные расходы, ведь PLINQ вынужден отслеживать позиции элементов и собирать результат в нужном порядке. На больших объёмах данных это заметно. Поэтому используйте метод только там, где порядок действительно нужен, а не по умолчанию.📍 Навигация: Вакансии • Задачи • Собесы
#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7❤2❤🔥2