Библиотека шарписта | C#, F#, .NET, ASP.NET
22K subscribers
2.71K photos
41 videos
85 files
5.08K links
Все самое полезное для C#-разработчика в одном канале.

По рекламе: @proglib_adv

Учиться у нас: https://proglib.io/w/b60af5a4

Для обратной связи: @proglibrary_feeedback_bot

РКН: https://gosuslugi.ru/snet/67a5c81cdc130259d5b7fead
Download Telegram
У «Библиотеки программиста» появился резервный канал в мессенджере MAX

Он нужен исключительно для связи с теми, кто не может следить за обновлениями здесь из-за трудностей с доступом. Поэтому, если вы видите это сообщение, распространите его среди жильцов вашего ЖЭКа.

Контент в MAX будет дублировать телеграмный — основной нашей площадкой был и остаётся Telegram. Надеемся, это временная мера.

Подписаться на «Библиотеку программиста» в MAX
😢31🌚12👍6❤‍🔥1🔥1
Кажется, мы окончательно перешли от игрушек к суровому AgentOps

Приглашаем на наш обновлённый курс по разработке ИИ-агентов. Никакой воды про «будущее нейросетей», только инженерный подход.

На курсе мы:

— пошагово строим готовые системы на LangGraph, CrewAI и MCP;
— настраиваем кэширование и роутинг, чтобы бот не сожрал токены;
— разбираемся со стейтом, учимся дебажить через time-travel и прикручиваем human-in-the-loop;
— выводим RAG в прод так, чтобы безопасники не завернули архитектуру из-за 152-ФЗ.

В пекло скучные лекции про общую инфраструктуру — сразу фокусируемся на агентных фреймворках и написании кода. Занятия ведут бывалые лиды из Газпромбанка и Альфы, набившие шишки на реальных задачах.

Кстати, на днях мы пилили агента в прямом эфире, если пропустили — есть запись вебинара.


Сегодня последний день, когда можно забрать курс по старым ценам. Базовый тариф сейчас стоит 49 000 ₽ (вместо 62 990 ₽), продвинутый трек — 99 000 ₽ (вместо 124 990 ₽). Если не хочется отдавать всю сумму сразу, есть рассрочка. Торопитесь — на потоке осталось всего 5 мест!

Зафиксировать цену и перейти к сборке своих агентов
2
📎 Делегаты в горячем коде очень голодные

Одна аллокация делегата выглядит безобидно. Но если она происходит миллион раз за сессию, это уже проблема.

for (int i = 0; i < 100_000; i++)
{
queue.Enqueue(() => Process(i));
}


Здесь каждая итерация создаёт новый объект делегата и захватывает переменную i в замыкание. То есть рантайм выделяет память дважды на каждый шаг цикла.

Сборщик мусора рано или поздно всё почистит, но частые мелкие аллокации создают давление на GC. Это выражается в микропаузах, просадках по latency и лишней работе, которую рантайм мог бы не делать.

Если делегат логически одинаков от вызова к вызову, то кешируйте его один раз:
private static readonly Action<Job> s_process = static job => Process(job);


static в лямбде явно запрещает захват внешних переменных. Если компилятор видит, что захват всё же происходит, он выдаст ошибку. Это хорошая защита от случайных замыканий.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
7👍2
🧑‍💻 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


В условиях работают &&, || и !, цепочки можно строить произвольной длины, вложенность тоже поддерживается.

Функция входит в F# 11. Чтобы включить, нужно указать --langversion:11.0.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
3🤩2👍1
👋 Сеньор занимается гейткипингом, а джун — на шадовинге

Это не баг и не новый фреймворк — это обычная рабочая неделя в IT-команде, просто описанная на внутреннем языке индустрии. Тех, кто этого языка не знает, легко обвести вокруг пальца или просто не заметить.

➡️ Изучайте словарь и больше не придётся гуглить в процессе разговора

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта
Please open Telegram to view this post
VIEW IN TELEGRAM
😁53
🗺 Подборка вакансий для шарпистов

C#/.NET-разработчик — от 250 000 ₽, удалёнка

Tech Lead (.NET) — от 350 000 ₽, удалёнка

Middle Unity (С#) developer — от 1 500 до 2 700 $, удалёнка

➡️ Еще больше топовых вакансий — в нашем канале C# Jobs

🐸 Библиотека шарписта
Please open Telegram to view this post
VIEW IN TELEGRAM
1
👨‍💻 F#: partitionWith для разбивки коллекций на части с разными типами

Стандартная partition делит коллекцию на две части, но обе остаются одного типа. Если нужно одновременно отфильтровать и преобразовать элементы — приходилось делать два прохода или писать свою функцию.

partitionWith принимает функцию-разделитель с типом 'T -> Choice<'T1, 'T2> и возвращает два списка, которые могут иметь разные типы элементов:
val inline partitionWith:
partitioner: ('T -> Choice<'T1, 'T2>) -> list: 'T list -> 'T1 list * 'T2 list


Удобно то, что total active pattern (|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.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
👍52
⭐️ Швейцарский нож отказоустойчивости для .NET

Polly — библиотека, которая позволяет описывать стратегии устойчивости: ретрай, circuit breaker, таймаут, rate limiter.

Быстрый старт
dotnet add package Polly.Core


Всё строится через ResiliencePipelineBuilder — стратегии комбинируются цепочкой и применяются в порядке добавления:
ResiliencePipeline pipeline = new ResiliencePipelineBuilder()
.AddRetry(new RetryStrategyOptions())
.AddTimeout(TimeSpan.FromSeconds(10))
.Build();

await pipeline.ExecuteAsync(async token => { /* ваша логика */ }, cancellationToken);


Реактивные стратегии

Retry — повторные попытки при сбое:
new RetryStrategyOptions
{
BackoffType = DelayBackoffType.Exponential,
UseJitter = true, // случайный разброс задержки
MaxRetryAttempts = 4,
Delay = TimeSpan.FromSeconds(3),
}


Circuit Breaker — разрывает связь при превышении порога ошибок:
new CircuitBreakerStrategyOptions
{
FailureRatio = 0.5,
// 50% ошибок...
SamplingDuration = TimeSpan.FromSeconds(10),
// ...за 10 секунд
MinimumThroughput = 8,
// при минимум 8 запросах
BreakDuration = TimeSpan.FromSeconds(30),
// разрываем на 30с
}


Fallback — возвращает запасное значение при сбое:
new FallbackStrategyOptions<UserAvatar>
{
FallbackAction = static args => Outcome.FromResultAsValueTask(UserAvatar.Blank)
}


Проактивные стратегии

Timeout — гарантирует, что ожидание не превысит лимит:
new ResiliencePipelineBuilder().AddTimeout(TimeSpan.FromSeconds(3));
// бросает TimeoutRejectedException при превышении


Rate Limiter — ограничивает интенсивность запросов:
// 100 запросов в минуту (скользящее окно)
.AddRateLimiter(new SlidingWindowRateLimiter(new SlidingWindowRateLimiterOptions
{
PermitLimit = 100,
Window = TimeSpan.FromMinutes(1)
}));


Polly де-факто стандарт во многих командах.

➡️ Репозиторий

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
👍15
🛠 Почему 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_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
12👍9
📎 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


out — метод обязан заполнить

Переменную инициализировать не нужно. Но компилятор требует, чтобы метод присвоил значение до возврата, иначе ошибка компиляции. Именно поэтому весь паттерн Try* в стандартной библиотеке построен на out.

bool TryParse(string s, out int result)


in — только читать, не трогать


Передаёт по ссылке — без копирования. Но запрещает запись. Нужен исключительно для производительности с большими структурами. С классами смысла нет — они и так передаются по ссылке.

float Dot(in Vector3 a, in Vector3 b) => ...


📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10🥱41
Почитали тут свежий отчёт по рынку ИИ-ускорителей в РФ: оказывается, 54% компаний тормозят внедрение ИИ исключительно из-за конских цен на инфраструктуру.

Ну, то есть написать пет-проект с вызовом API это задача на вечер, а вот запустить агента в продакшн так, чтобы он не сжёг бюджет отдела за неделю — суровая инженерия.

По сути, сейчас мало уметь собирать RAG. Нужно считать токены, настраивать time-travel дебаг в LangGraph и уметь роутить запросы на лету. Всё это мы учли в обновлённом курсе по разработке AI-агентов, где акцент сделан именно на AgentOps и жёсткий контроль ресурсов.

Также в программе:

— оценка качества, трейсинг и защита от деградации пайплайнов;
— мультиагентные паттерны и интеграция по протоколу MCP;
— локальный деплой Open Source под 152-ФЗ (когда данные нельзя выносить наружу).

Кажется, это единственный адекватный roadmap по переходу от блокнотов к enterprise-решениям.

Прямо сейчас можно урвать курс с увесистой скидкой (49 000 ₽ 62 990 ₽ за базовый тариф и 99 000 ₽ 124 990 ₽ за продвинутый трек), но стоит поторопиться — на потоке осталось всего 5 мест.

👉 Зафиксировать цену и начать собирать агентов, за которых не стыдно в проде
😁52
🤖 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 и улучшает инструкции и документацию. Система становится умнее после каждого цикла.

Для поиска фикса используются четыре модели последовательно — каждая предлагает независимый подход, тестирует его, записывает результат. После первого раунда модели видят чужие попытки и предлагают новые идеи. Одновременный доступ к одним файлам и одному устройству всё сломает.

➡️ Блог разработчиков

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#пульс_индустрии
Please open Telegram to view this post
VIEW IN TELEGRAM
4🔥2
🤨 Вопрос про стриминг на собесе

Эндпоинт возвращает тысячи записей. Что происходит, если просто сделать return Ok(list)?

Правильно — всё копится в памяти. Сервер ждёт, пока соберётся весь ответ, только потом отправляет клиенту. При высокой нагрузке это убивает и память, и растёт задержка.

Возникает вопрос:
Как не держать весь ответ в памяти и какими инструментами воспользоваться?


Данные можно отдавать данные по мере готовности, без лишних аллокаций и с контролем над потоком.

➡️ Ответ в библиотеке собеса по C#

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#dotnet_challenge
Please open Telegram to view this post
VIEW IN TELEGRAM
7
😈 Ручное тестирование мертво

Металлург → Fullstack QA Engineer в Альфа-Банке. Звучит нереально, но именно такой путь описан в новой статье.

Путь начался в 2021 с первого собеседования в IT без понимания, зачем нужен бэкенд. Финал: тестирование аналитических HTAP-систем, автотесты на Java и работа с Kafka-потоками.

➡️ В статье собрано 5 советов, что работает на рынке прямо сейчас

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
🧑‍💻 Пусть приложение падает при старте, а не в 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);
}


Шаг второй. Регистрируем в Program.cs с вызовом 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; }
}
}
}


Паттерн не новый, но cтоит использовать везде, где конфиг критичен для работы сервиса.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
👍25
⚡️ Утечка памяти, которую не видно до прода

Channel<T> — это стандартный выбор для producer-consumer в .NET. Быстрее ConcurrentQueue, дружит с cancellation, не аллоцирует лишнего. Документация рекомендует. Коллеги используют.

Дефолтный способ создать канал выглядит так:
var channel = Channel.CreateUnbounded<WorkItem>();


Канал принимает записи бесконечно. Никаких исключений, никаких предупреждений, никаких логов. Пока producer пишет быстрее, чем consumer успевает читать, очередь просто растёт в хипе.

В разработке нагрузка маленькая, producer и consumer держат примерно одинаковый темп. На проде, при трафике чуть выше нормы, разрыв может быть огромным.

Microsoft сделала такой дефолт намеренно: в некоторых сценариях потеря записи хуже, чем рост памяти. Логирование, например. Но для платёжных событий или команд это выбор, который стоит делать осознанно, а не получать по умолчанию.

Переходим на CreateBounded и явно выбираем поведение при переполнении:
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
capacity = пик записи в секунду × P99 время обработки в секундах  × 2


Пример: 500 записей в секунду, p99 обработки 200 мс:
capacity = 500 × 0.2 × 2 = 200


📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#il_люминатор
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥122
💡 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
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4