23284
Все самое полезное для C#-разработчика в одном канале. По рекламе: @proglib_adv Учиться у нас: https://proglib.io/w/b60af5a4 Для обратной связи: @proglibrary_feeedback_bot РКН: https://gosuslugi.ru/snet/67a5c81cdc130259d5b7fead
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#garbage_collector
🤔 Запросы в БД быстрые, кэш есть, async везде, но приложение тормозит
Скорее всего виноват не код, а аллокации.
Каждый раз, когда вы пишете Substring(), Split() или ToArray(), то вы создаёте новые объекты на хипе. При большой нагрузке это тысячи объектов в секунду.
А дальше приходит Garbage Collector и делает паузу. Вот откуда те самые загадочные всплески задержек.
Что делать:
→ Используйте Span<T> и ReadOnlySpan<T> вместо substring
→ ArrayPool<T>.Shared.Rent() вместо new массивов
→ StringBuilder пулить через ObjectPool
→ Профилируйте через dotnet-trace + BenchmarkDotNet
Правило простое: меньше объектов на хипе → реже GC → стабильная латентность. Иногда пара изменений убирает 80% тормозов.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#sharp_view
⚙️ yield return не бесплатный
Итераторы выглядят просто, но работают иначе:
IEnumerable<int> GetIds()
{
foreach (var item in _items)
yield return item.Id;
}
yield return это правильный выбор. Код чище, API удобнее, читаемость выше.void FillIds(Span<int> buffer)
{
for (int i = 0; i < _items.Count; i++)
buffer[i] = _items[i].Id;
}
List<int> GetIds()
{
var result = new List<int>(_items.Count);
foreach (var item in _items)
result.Add(item.Id);
return result;
}
for (int i = 0; i < _items.Count; i++)
Process(_items[i].Id);
yield return стоит использовать тогда, когда он делает API лучше и код понятнее. Но не стоит считать его нулевым по стоимости. Если участок горячий, то сначала замерьте, потом решайте.
👨💻 Конфигурация везде это проблема
Когда конфигурация разбросана по всему коду, проект становится хрупким. Строки вроде _configuration["Stripe:ApiKey"] встречаются в контроллерах, сервисах, хелперах — и каждый раз с риском опечатки, без типизации, без валидации. Поменяли ключ в appsettings — ищете, где всё сломалось.
Что не так с таким подходом
Прямое обращение к IConfiguration через магические строки создаёт несколько реальных проблем. Нет единого места, где видно всю конфигурацию приложения. Парсинг типов (int.Parse, bool.Parse) повторяется в разных местах. Опечатка в ключе не вызовет ошибку компиляции — только падение в рантайме.
Тестировать такой код неудобно:
// Так делать не стоит — магические строки, парсинг вручную
var apiKey = _configuration["Stripe:ApiKey"];
var timeout = int.Parse(_configuration["Timeout"] ?? "30");
// Infrastructure/Configuration/ApplicationOptions.cs
public sealed class ApplicationOptions
{
public PaymentOptions Payment { get; set; } = new();
public DatabaseOptions Database { get; set; } = new();
public CacheOptions Cache { get; set; } = new();
public FeatureFlags Features { get; set; } = new();
}
public sealed class PaymentOptions
{
public string StripeApiKey { get; set; } = string.Empty;
public string StripeWebhookSecret { get; set; } = string.Empty;
public int RetryAttempts { get; set; } = 3;
}
Program.cs одна привязка для всего:builder.Services.AddOptions<ApplicationOptions>()
.Bind(builder.Configuration)
.ValidateDataAnnotations()
.ValidateOnStart();
ValidateOnStart выбрасывает исключение при запуске, если конфигурация невалидна. Не в рантайме, не при первом запросе — сразу при старте.Program.cs, выносим регистрацию в extension method и сразу открываем под-опции для инжекции:public static class OptionsExtensions
{
public static IServiceCollection AddApplicationOptions(
this IServiceCollection services,
IConfiguration configuration)
{
services.AddOptions<ApplicationOptions>()
.Bind(configuration)
.ValidateDataAnnotations()
.ValidateOnStart();
services.AddSingleton(sp =>
sp.GetRequiredService<IOptions<ApplicationOptions>>().Value.Payment);
services.AddSingleton(sp =>
sp.GetRequiredService<IOptions<ApplicationOptions>>().Value.Database);
return services;
}
}
IOptions<T>. Инжектируем напрямую:public class OrderService
{
private readonly PaymentOptions _paymentOptions;
private readonly DatabaseOptions _dbOptions;
public OrderService(
PaymentOptions paymentOptions,
DatabaseOptions dbOptions)
{
_paymentOptions = paymentOptions;
_dbOptions = dbOptions;
}
}
👨💻 Хватит мучить MongoDB через EF Core
Вы выбрали MongoDB ради гибкости. А потом два часа потратили на то, чтобы EF Core позволил сделать элементарный фильтр. Поздравляем, вы сами себе враг.
EF Core для MongoDB это иллюзия комфорта. Знакомый синтаксис, но в обмен на всё, ради чего вообще брали Mongo: агрегационные пайплайны, геозапросы, полнотекстовый поиск. Провайдер этого просто не умеет.
Правильный путь — это MongoDB.Driver напрямую.
Ставите пакет:
dotnet add package MongoDB.Driver
[CollectionName("orders")]
public class Order
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public string CustomerName { get; set; }
}public class MongoRepository<T> : IMongoRepository<T>
{
private readonly IMongoCollection<T> _collection;
public MongoRepository(IMongoDatabase db)
{
var name = typeof(T)
.GetCustomAttribute<CollectionNameAttribute>()?.Name
?? typeof(T).Name;
_collection = db.GetCollection<T>(name);
}
}
services.AddScoped(typeof(IMongoRepository<>), typeof(MongoRepository<>));
[CollectionName]. var filter = Builders<Order>.Filter.Eq(o => o.Status, "pending");
var sort = Builders<Order>.Sort.Descending(o => o.CreatedAt);
var results = await _collection.Find(filter).Sort(sort).ToListAsync();
💡 Generative AI for Beginners .NET v2
Microsoft выпустила вторую версию бесплатного открытого курса «Generative AI for Beginners .NET». Это не обновление старого материала, а полностью новый курс с другой структурой и свежими примерами.
Курс рассчитан на .NET-разработчиков, которые хотят разобраться в генеративном ИИ — от основ до работающих паттернов в продакшене.
В первой версии основой был Semantic Kernel. В v2 его заменили на Microsoft.Extensions.AI (MEAI). MEAI входит в экосистему .NET 10, следует тем же принципам, что ILogger и IConfiguration, и не привязывает к конкретному оркестратору.
➡️ Репозиторий | Блог разработчиков
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#async_news
🧑💻 Пусть приложение падает при старте, а не в 2 часа ночи
Представьте сценарий. Платёжный сервис ушёл в прод. Конфиг собран наспех, API-ключ не тот, URL без HTTPS. Всё тихо до первой реальной транзакции. Потом звонок в ночь, инцидент, откат.
Паттерн Options с валидацией на старте решает именно эту проблему.
Вместо того чтобы читать конфиг в рантайме и падать где попало, мы проверяем всё один раз при запуске. Если что-то не так, то приложение не поднимается вообще. Это лучше, чем ловить NPE или невалидный URL в середине бизнес-логики.
Шаг первый. Описываем класс опций с атрибутами валидации:
public sealed class PaymentGatewayOptions
{
[Required(ErrorMessage = "API Key is required - check your key")]
[StringLength(100, MinimumLength = 20)]
public string ApiKey { get; set; } = string.Empty;
[Required]
[Range(1, 10, ErrorMessage = "Retry count must be between 1 and 10")]
public int MaxRetries { get; set; } = 3;
[Required]
[RegularExpression(@"^https://", ErrorMessage = "Base URL must use HTTPS")]
public string BaseUrl { get; set; } = string.Empty;
[Required]
public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(30);
}
ValidateOnStart():builder.Services.AddOptions<PaymentGatewayOptions>()
.BindConfiguration("PaymentGateway")
.ValidateDataAnnotations()
.ValidateOnStart(); // упадёт при старте, если конфиг невалиден
IOptions<T>:public class PaymentService
{
private readonly PaymentGatewayOptions _options;
public PaymentService(IOptions<PaymentGatewayOptions> options)
{
_options = options.Value;
}
public async Task ProcessPaymentAsync(decimal amount)
{
using var client = new HttpClient
{
BaseAddress = new Uri(_options.BaseUrl),
Timeout = _options.Timeout
};
client.DefaultRequestHeaders.Add("X-API-Key", _options.ApiKey);
for (int i = 0; i < _options.MaxRetries; i++)
{
try { /* логика запроса */ }
catch { if (i == _options.MaxRetries - 1) throw; }
}
}
}
📈 Дайджест недели
Мы уже готовим для вас шутки на 1 апреля, а пока что дайджест.
— AI-агенты в .NET MAUI
— Сеньор занимается гейткипингом
— Утилита, которая возвращает правый Ctrl вместо кнопки Copilot
— GitHub Copilot теперь умеет мигрировать .NET-проекты
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#async_news
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#garbage_collector
🤖 AI-агенты в .NET MAUI
Контрибьют в .NET MAUI исторически требовал огромных затрат времени даже на простые баги: воспроизведение проблемы: 30–60 минут, поиск причины: 1–3 часа, реализация фикса ещё до двух часов, написание тестов ещё два. Итого 4–8 часов на один issue, и это у опытных разработчиков.
Команда Syncfusion опубликовала разбор того, как они встроили кастомных AI-агентов в процесс разработки.
Что построили
Набор специализированных агентов и переиспользуемых навыков, которые покрывают полный цикл:pr-review skill — четырёхфазный процесс: анализ issue, проверка наличия тестов, до четырёх попыток фикса разными моделями, финальный отчёт с объяснением выбора лучшего подхода.write-tests-agent — анализирует issue и выбирает нужный тип тестов: UI-тесты через Appium или XAML-тесты. Обязательно проверяет, что тест падает без фикса и проходит с ним.sandbox-agent — ручная валидация в Sandbox-приложении. Для сценариев, которые трудно автоматизировать: визуальные баги, сложные жесты, поворот экрана.learn-from-pr agent — анализирует смёрженные PR и улучшает инструкции и документацию. Система становится умнее после каждого цикла.
Для поиска фикса используются четыре модели последовательно — каждая предлагает независимый подход, тестирует его, записывает результат. После первого раунда модели видят чужие попытки и предлагают новые идеи. Одновременный доступ к одним файлам и одному устройству всё сломает.
➡️ Блог разработчиков
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#пульс_индустрии
📎 ref vs out vs in
C# передаёт параметры по значению — метод получает копию, оригинал не меняется. Иногда это не то, что нужно.
ref — вы даёте, метод меняет, вы получаете обратно
Переменная должна быть инициализирована до вызова. Метод может изменить её — а может и не изменить, это не обязательно:
void Add(ref int total, int value) => total += value;
int sum = 0;
Add(ref sum, 5); // sum == 5
Add(ref sum, 3); // sum == 8
bool TryParse(string s, out int result)
float Dot(in Vector3 a, in Vector3 b) => ...
👩💻 Открытый урок «Анатомия памяти: типы данных, способы хранения, аллокации и работа GC»
🗓 2 апреля в 20:00 МСК
🆓 Занятие посвящено разбору данных вопросов. От понимания жизненного цикла переменной и того, как она хранится в памяти приложения, напрямую зависит его производительность и работоспособность. Мы обязательно разберем эти вопросы на нашем занятии.
На открытом уроке рассмотрим:
✔️ Рассмотрим фрагменты памяти, из которых состоит память .net-приложения, поговорим о том какие бывают типы;
✔️ Узнаем что из себя представляют процессы упаковки и распаковки;
✔️ Посмотрим на нюансы работы с управляемой кучей;
✔️ Разберем принципы работы сборки мусора.
🔗 Ссылка на регистрацию: https://clc.to/0Bt0-Q
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
👨💻 F#: `partitionWith` для разбивки коллекций на части с разными типами
Стандартная partition делит коллекцию на две части, но обе остаются одного типа. Если нужно одновременно отфильтровать и преобразовать элементы — приходилось делать два прохода или писать свою функцию.
partitionWith принимает функцию-разделитель с типом 'T -> Choice<'T1, 'T2> и возвращает два списка, которые могут иметь разные типы элементов:
val inline partitionWith:
partitioner: ('T -> Choice<'T1, 'T2>) -> list: 'T list -> 'T1 list * 'T2 list
(|A|B|) уже имеет сигнатуру 'T -> Choice<'T1, 'T2>, поэтому его можно передать напрямую:let (|Valid|Invalid|) (s: string) =
match System.Int32.TryParse s with
| true, n -> Valid n
| _ -> Invalid s
let parsed, errors =
["42"; "hello"; "7"; "oops"; "99"]
|> List.partitionWith (|Valid|Invalid|)
// parsed = [42; 7; 99]
// errors = ["hello"; "oops"]
Array, List, Set и Array.Parallel.
👋 Сеньор занимается гейткипингом, а джун — на шадовинге
Это не баг и не новый фреймворк — это обычная рабочая неделя в IT-команде, просто описанная на внутреннем языке индустрии. Тех, кто этого языка не знает, легко обвести вокруг пальца или просто не заметить.
➡️ Изучайте словарь и больше не придётся гуглить в процессе разговора
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
📎 Делегаты в горячем коде очень голодные
Одна аллокация делегата выглядит безобидно. Но если она происходит миллион раз за сессию, это уже проблема.
for (int i = 0; i < 100_000; i++)
{
queue.Enqueue(() => Process(i));
}
i в замыкание. То есть рантайм выделяет память дважды на каждый шаг цикла. private static readonly Action<Job> s_process = static job => Process(job);
😱 Если ваш продукт не умеет отдавать данные в формате, понятном AI-агенту, то вас просто не существует
Скрипт не будет кликать по красивым кнопкам в браузере, он уйдёт к конкуренту с нормальным API. Перестроить архитектуру под машинных клиентов — это уже не хайп, а необходимое условие сохранения конкурентоспособности.
Как адаптировать продукт и не исчезнуть из выдачи:
— интегрировать MCP и A2A-взаимодействие, чтобы агенты могли вас читать;
— научиться контролировать стоимость (лимиты, кэш, роутинг между моделями);
— настроить AgentOps: трейсинг, логирование и отлов регрессий.
Всё это ждёт вас на обновлённом курсе «Разработка AI-агентов». Мы специально сделали фокус на утилитарном инжиниринге и production-ready решениях.
Кстати, до 29 марта можно забрать курс с большой скидкой, и стоит поторопиться — мест на потоке всё меньше.
Зафиксировать цену и начать деплоить агентов без слива бюджета 👈
🕸 Пять типичных ошибок при проектировании интеграции с помощью Kafka
Система работала нормально, пока обработка батча не заняла слишком много времени. Координатор консьюмер-групп решил, что консьюмер мёртв, отверг коммит оффсета — и консьюмер начал читать сообщения заново. По кругу.
Это называется ложная ребалансировка, и это один из самых неочевидных сценариев поломки.
Архитектор Альфа-Банка разобрал пять таких историй из реального прода — с объяснением механики и тем, как этого избежать.
➡️ Читать статью
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
🔎 Удалённое комбо вакансий для шарпистов
Подобрали три вакансии для тех, кто не хочет выходить из дома:
.NET-разработчик
C#/.NET-разработчик
Senior Backend .NET Engineer
➡️ Еще больше топовых вакансий — в нашем канале C# Jobs
🐸 Библиотека шарписта
🤨 10 месяцев с Copilot Coding Agent в dotnet/runtime
Microsoft опубликовали детальный разбор использования GitHub Copilot Coding Agent в репозитории dotnet/runtime за последние 10 месяцев. Это один из самых сложных проектов в мире: основа .NET, 14 миллионов строк кода, миллионы активных разработчиков. Репозиторий использовали как полигон для эксперимента: может ли AI-агент полноценно участвовать в разработке такого масштаба?
Общая картина: 878 PR за 10 месяцев
С мая 2025 по март 2026 команда открыла 878 PR через CCA. Из них принято 535 (67.9%), закрыто 253, ещё 90 остаются открытыми.
Для сравнения: PR от разработчиков Microsoft принимаются в 87.1% случаев, от сообщества — в 79.7%, от ботов вроде dependabot — в 85.9%. CCA уступает людям по этому показателю, но авторы подчёркивают: это разные задачи.
Важный показатель качества — процент откатов. Из 535 принятых PR через CCA откатили 3 (0.6%). Среди остальных PR — 33 из 4251 (0.8%). Разница статистически незначима, но это говорит о том, что AI-код не хуже по качеству в плане регрессий.
Главный урок: инструкции важнее модели
До появления файла .github/copilot-instructions.md успешность выполнения задач была 38%. После — 69%. Это самый показательный факт во всей статье.
Изначально CCA не мог скачать NuGet-пакеты, потому что firewall блокировал внешние ресурсы. Не знал, как собирать репозиторий. Не мог запустить тесты. Это было похоже на то, как взять нового разработчика и поставить ему задачу, не дав доступа ни к чему.
Основные выводы авторов: думайте об агенте как о паре, а не замене; задачи с чётким описанием и понятным скоупом — лучшие кандидаты; ожидайте несколько итераций; следите за «ленивостью» агента, который делает минимум и не экстраполирует; каждый урок, вложенный в файл инструкций, окупается на всех следующих PR.
➡️ Блог разработчиков
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#async_news
⚡️ Прячем секреты и не ломаем тесты
Одна из самых частых причин инцидентов на проде это конфиг, который не зависит от окружения. Кто-то закоммитил строку подключения к боевой базе, кто-то запустил тесты против продакшн-платёжки. Паттерн Options хорош сам по себе, но без правильной иерархии конфигов он не спасёт.
.NET читает конфигурацию слоями. Каждый следующий слой перекрывает предыдущий.
В Program.cs это выглядит так:
var builder = WebApplication.CreateBuilder(args);
builder.Configuration
// 1. Базовые настройки — коммитим в репо, только безопасные дефолты
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
// 2. Настройки под окружение — Development.json коммитим, Production.json нет
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json",
optional: true, reloadOnChange: true)
// 3. User Secrets — только для разработки, никогда не коммитим
.AddUserSecrets<Program>(optional: true)
// 4. Переменные окружения — CI/CD, Docker, Kubernetes
.AddEnvironmentVariables()
// 5. Хранилище секретов — для прода
.AddVaultSecrets(builder.Configuration);
├── appsettings.json # безопасные дефолты, коммитим
├── appsettings.Development.json # локальные настройки, коммитим
├── appsettings.Production.json # только структура без значений, коммитим
└── secrets.json # User Secrets, в .gitignore
{
"Database": {
"ConnectionString": "CONFIGURED_IN_SECRETS_MANAGER"
},
"PaymentGateway": {
"ApiKey": "CONFIGURED_IN_SECRETS_MANAGER"
}
}# .gitlab-ci.yml
deploy_production:
stage: deploy
script:
- dotnet publish -c Release
- ./deploy.sh
variables:
Database__ConnectionString: $PROD_DB_CONNECTION
PaymentGateway__ApiKey: $STRIPE_API_KEY
only:
- main
⚡️ Утечка памяти, которую не видно до продаChannel<T> — это стандартный выбор для producer-consumer в .NET. Быстрее ConcurrentQueue, дружит с cancellation, не аллоцирует лишнего. Документация рекомендует. Коллеги используют.
Дефолтный способ создать канал выглядит так:
var channel = Channel.CreateUnbounded<WorkItem>();
var channel = Channel.CreateBounded<WorkItem>(
new BoundedChannelOptions(capacity: 500)
{
FullMode = BoundedChannelFullMode.Wait,
SingleReader = true
SingleWriter = false
}
);
BoundedChannelFullMode это настоящее архитектурное решение. Четыре варианта с разным поведением по отношению к потере данных:Wait — блокирует producer до появления места DropNewest — выбрасывает только что записанное DropOldest — выбрасывает самое старое DropWrite — возвращает false на TryWrite capacity = пик записи в секунду × P99 время обработки в секундах × 2
capacity = 500 × 0.2 × 2 = 200
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#garbage_collector
😈 Ручное тестирование мертво
Металлург → Fullstack QA Engineer в Альфа-Банке. Звучит нереально, но именно такой путь описан в новой статье.
Путь начался в 2021 с первого собеседования в IT без понимания, зачем нужен бэкенд. Финал: тестирование аналитических HTAP-систем, автотесты на Java и работа с Kafka-потоками.
➡️ В статье собрано 5 советов, что работает на рынке прямо сейчас
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
🤨 Вопрос про стриминг на собесе
Эндпоинт возвращает тысячи записей. Что происходит, если просто сделать return Ok(list)?
Правильно — всё копится в памяти. Сервер ждёт, пока соберётся весь ответ, только потом отправляет клиенту. При высокой нагрузке это убивает и память, и растёт задержка.
Возникает вопрос:
Как не держать весь ответ в памяти и какими инструментами воспользоваться?
Почитали тут свежий отчёт по рынку ИИ-ускорителей в РФ: оказывается, 54% компаний тормозят внедрение ИИ исключительно из-за конских цен на инфраструктуру.
Ну, то есть написать пет-проект с вызовом API это задача на вечер, а вот запустить агента в продакшн так, чтобы он не сжёг бюджет отдела за неделю — суровая инженерия.
По сути, сейчас мало уметь собирать RAG. Нужно считать токены, настраивать time-travel дебаг в LangGraph и уметь роутить запросы на лету. Всё это мы учли в обновлённом курсе по разработке AI-агентов, где акцент сделан именно на AgentOps и жёсткий контроль ресурсов.
Также в программе:
— оценка качества, трейсинг и защита от деградации пайплайнов;
— мультиагентные паттерны и интеграция по протоколу MCP;
— локальный деплой Open Source под 152-ФЗ (когда данные нельзя выносить наружу).
Кажется, это единственный адекватный roadmap по переходу от блокнотов к enterprise-решениям.
Прямо сейчас можно урвать курс с увесистой скидкой (49 000 ₽ 62 990 ₽ за базовый тариф и 99 000 ₽ 124 990 ₽ за продвинутый трек), но стоит поторопиться — на потоке осталось всего 5 мест.
👉 Зафиксировать цену и начать собирать агентов, за которых не стыдно в проде
🛠 Почему C# сравнивает списки по ссылке — и это правильно
Многие удивляются: почему list1.Equals(list2) возвращает false, даже если содержимое одинаковое?
Это не баг и не лень. Это осознанное решение.
Причина 1: равенство коллекций неоднозначно[1, 2, 3] и [3, 2, 1] — равны? Зависит от задачи:
— порядок важен → нет
— порядок не важен → да
— разные типы (List vs Stack) → может быть
Нет одного правильного ответа, поэтому язык не навязывает ни один.
Причина 2: производительность
Равенство ссылок — O(1).
Сравнение по содержимому — O(n) или O(n log n).
В ранние годы C# строился на скорости.
Причина 3: мутабельность всё ломает
Допустим, вы сравнили два списка, получили true, сохранили флаг. Потом кто-то добавил элемент в один из них. Флаг стал false. равеноство на изменяемых объектах концептуально сломано.
Как правильно
Реализуйте IEqualityComparer<IEnumerable<T>> под свою задачу:
— SequenceComparer — порядок важен
— ContentComparer — порядок не важен
Вы сами выбираете семантику. Язык не угадывает за вас.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#il_люминатор
⭐️ Швейцарский нож отказоустойчивости для .NET
Polly — библиотека, которая позволяет описывать стратегии устойчивости: ретрай, circuit breaker, таймаут, rate limiter.
Быстрый старт
dotnet add package Polly.Core
ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
.AddRetry(new RetryStrategyOptions())
.AddTimeout(TimeSpan.FromSeconds(10))
.Build();
await pipeline.ExecuteAsync(async token => { /* ваша логика */ }, cancellationToken);
new RetryStrategyOptions
{
BackoffType = DelayBackoffType.Exponential,
UseJitter = true, // случайный разброс задержки
MaxRetryAttempts = 4,
Delay = TimeSpan.FromSeconds(3),
}
new CircuitBreakerStrategyOptions
{
FailureRatio = 0.5,
// 50% ошибок...
SamplingDuration = TimeSpan.FromSeconds(10),
// ...за 10 секунд
MinimumThroughput = 8,
// при минимум 8 запросах
BreakDuration = TimeSpan.FromSeconds(30),
// разрываем на 30с
}
new FallbackStrategyOptions<UserAvatar>
{
FallbackAction = static args => Outcome.FromResultAsValueTask(UserAvatar.Blank)
}
new ResiliencePipelineBuilder().AddTimeout(TimeSpan.FromSeconds(3));
// бросает TimeoutRejectedException при превышении
// 100 запросов в минуту (скользящее окно)
.AddRateLimiter(new SlidingWindowRateLimiter(new SlidingWindowRateLimiterOptions
{
PermitLimit = 100,
Window = TimeSpan.FromMinutes(1)
}));
🗺 Подборка вакансий для шарпистов
C#/.NET-разработчик — от 250 000 ₽, удалёнка
Tech Lead (.NET) — от 350 000 ₽, удалёнка
Middle Unity (С#) developer — от 1 500 до 2 700 $, удалёнка
➡️ Еще больше топовых вакансий — в нашем канале C# Jobs
🐸 Библиотека шарписта
🧑💻 F# 11: поддержка #elif в препроцессорных директивах
Раньше при условной компиляции под несколько платформ приходилось вкладывать #if друг в друга. Чем больше веток — тем глубже вложенность и тяжелее читать код.
Теперь F# поддерживает #elif, и цепочки условий пишутся плоско:
#if WIN64
let path = "/library/x64/runtime.dll"
#elif WIN86
let path = "/library/x86/runtime.dll"
#elif MAC
let path = "/library/iOS/runtime-osx.dll"
#else
let path = "/library/unix/runtime.dll"
#endif
--langversion:11.0.
Кажется, мы окончательно перешли от игрушек к суровому AgentOps
Приглашаем на наш обновлённый курс по разработке ИИ-агентов. Никакой воды про «будущее нейросетей», только инженерный подход.
На курсе мы:
— пошагово строим готовые системы на LangGraph, CrewAI и MCP;
— настраиваем кэширование и роутинг, чтобы бот не сожрал токены;
— разбираемся со стейтом, учимся дебажить через time-travel и прикручиваем human-in-the-loop;
— выводим RAG в прод так, чтобы безопасники не завернули архитектуру из-за 152-ФЗ.
В пекло скучные лекции про общую инфраструктуру — сразу фокусируемся на агентных фреймворках и написании кода. Занятия ведут бывалые лиды из Газпромбанка и Альфы, набившие шишки на реальных задачах.
Кстати, на днях мы пилили агента в прямом эфире, если пропустили — есть запись вебинара.