ch
Feedback
Библиотека шарписта | C#, F#, .NET, ASP.NET

Библиотека шарписта | C#, F#, .NET, ASP.NET

前往频道在 Telegram

Все самое полезное для C#-разработчика в одном канале. По рекламе: @proglib_adv Учиться у нас: https://proglib.io/w/b60af5a4 Для обратной связи: @proglibrary_feeedback_bot РКН: https://gosuslugi.ru/snet/67a5c81cdc130259d5b7fead

显示更多

📈 Telegram 频道 Библиотека шарписта | C#, F#, .NET, ASP.NET 的分析概览

频道 Библиотека шарписта | C#, F#, .NET, ASP.NET (@csharpproglib) 俄语 语言赛道中的 是活跃参与者。目前社区聚集了 21 873 名订阅者,在 技术与应用 类别中位列第 6 218,并在 俄罗斯 地区排名第 30 852

📊 受众指标与增长动态

невідомо 创建以来,项目保持高速增长,吸引了 21 873 名订阅者。

根据 09 六月, 2026 的最新数据,频道保持稳定运转。过去 30 天订阅人数变化为 -88,过去 24 小时变化为 -7,整体触达仍然可观。

  • 认证状态: 未认证
  • 互动率 (ER): 平均受众互动率为 11.86%。内容发布后 24 小时内通常能获得 7.09% 的反应,占订阅者总量。
  • 帖子覆盖: 每篇帖子平均可获得 2 594 次浏览,首日通常累积 1 550 次浏览。
  • 互动与反馈: 受众积极参与,单帖平均反应数为 9
  • 主题关注点: 内容集中在 .net, шарписта, навигация, await, string 等核心主题上。

📝 描述与内容策略

作者将该频道定位为表达主观观点的平台:
Все самое полезное для C#-разработчика в одном канале. По рекламе: @proglib_adv Учиться у нас: https://proglib.io/w/b60af5a4 Для обратной связи: @proglibrary_feeedback_bot РКН: https://gosuslugi.ru/snet/67a5c81cdc130259d5b7fead

凭借高频更新(最新数据采集于 10 六月, 2026),频道始终保持新鲜度与高覆盖。分析显示受众积极互动,使其成为 技术与应用 类别中的关键影响点。

21 873
订阅者
-724 小时
-187
-8830
帖子存档
👀 Разрешение перегрузок в C# Фрагмент кода выглядит как задача с подвохом. Два метода, оба принимают null. Какой вызовется:
void Print(string text) => Console.WriteLine("String");
void Print(object obj) => Console.WriteLine("Object");

Print(null);
Ответ: выведется "String". И это не случайность, а предсказуемое поведение разрешения перегрузок в C#. Компилятор видит два кандидата. null совместим и со string, и с object, потому что оба являются ссылочными типами и принимают null. Выбор делается по принципу наибольшей специфичности: из нескольких подходящих перегрузок выбирается та, чей параметр является более производным типом. string наследует от object, значит string более специфичный тип. Это поведение описано в спецификации C# как часть алгоритма разрешения перегрузок. Когда один тип параметра неявно конвертируется в другой, побеждает более конкретный. Чтобы явно указать нужную перегрузку, достаточно привести null к нужному типу:
Print((object)null);  // выведет "Object"
Print((string)null);  // выведет "String"
📍 Навигация: ВакансииЗадачиСобесы 🐸 Библиотека шарписта #il_люминатор

✌🏻 У нас две новости — хорошая и плохая! Хорошая: Ваших знаний, скорее всего, хватит, чтобы собрать рабочую демку AI-агента в Colab. 🫡 Плохая: Вы вряд ли выведете его в прод, не обанкротившись на токенах и не слив базу. 🤯 Для защиты от таких сценариев мы полностью пересобрали курс «Разработка AI-агентов». Теперь внутри плотная работа с экономикой ресурсов, дебаг через time-travel в LangGraph, извлечение данных из кривых сканов для RAG и комплаенс по 152-ФЗ.
Если всё ещё сомневаетесь, послушайте голосовое от спикера курса Влада Прошинского, где он объясняет, как правильно тестировать агентов перед релизом.
Программа курса, полный состав спикеров и другие подробности 👈🏻 ВАЖНО! До 5 апреля на курс действует скидка, но свободные места могут закончиться раньше.

💡 Фиксируйте архитектурные решения прямо в репозитории Architectural Decision Records это короткие Markdown-файлы, которые ф
💡 Фиксируйте архитектурные решения прямо в репозитории Architectural Decision Records это короткие Markdown-файлы, которые фиксируют контекст, само решение и последствия. Не многостраничная спецификация, а что-то ближе к протоколу встречи. Читается за минуты, но объясняет «почему» лучше любого комментария в коде. Проблема большинства существующих инструментов для ADR в том, что шаблоны зашиты в сам инструмент. Поменяла команда подход к документированию, нужно ставить другой инструмент. dotnet-adr это .NET Global Tool, который отделяет сам инструмент от шаблонов. Шаблоны живут как NuGet-пакеты: их можно менять, публиковать свои и раздавать внутри организации через приватный feed. Установка:
dotnet tool install -g adr
Подключаем стандартный пакет шаблонов:
adr templates package set adr.templates
adr templates package install
Создаём первый ADR:
adr new "Use PostgreSQL instead of MongoDB"
Инструмент создаст нумерованный Markdown-файл с заголовком и структурой из выбранного шаблона. По умолчанию файлы складываются в docs/adr, но путь настраивается через adr.config.json в корне репозитория:
{
  "path": "./Docs/Adr"
}
Если одно решение заменяет другое, это фиксируется явно:
adr new "Switch to Cosmos DB" -i 3
Третий ADR получит статус «superseded», новый сошлётся на него. Инструмент подходит тем, кто работает в .NET-экосистеме и хочет хранить архитектурные решения рядом с кодом, не усложняя процесс. ➡️ Репозиторий 📍 Навигация: ВакансииЗадачиСобесы 🐸 Библиотека шарписта #sharp_view

📎 Task.Run внутри ASP.NET пайплайна Один из самых распространённых антипаттернов в .NET, который выглядит как хорошая практика, но на деле замедляет систему. Обычный код:
await Task.Run(() => _logger.LogInformation("Processing..."));
await Task.Run(() => MapToDto(entity));
await Task.Run(() => ValidateHeaders(request));
Выглядит современно и async везде. Что происходит на самом деле Каждый Task.Run внутри ASP.NET запроса: — ставит задачу в очередь thread pool — вызывает context switch — добавляет scheduling overhead При этом ASP.NET уже работает на оптимально управляемом thread pool. Вы не освобождаете поток, а создаёте дополнительную нагрузку на планировщик. Как надо:
// Логирование — всегда синхронно
_logger.LogInformation("Processing...");

// Маппинг — синхронно
var dto = MapToDto(entity);

// Валидация заголовков — синхронно
ValidateHeaders(request);

// async оставляем только для реального I/O
var data = await _repository.GetAsync(id);
var response = await _httpClient.GetAsync(url);
Есть ситуация, где он оправдан: долгая CPU-bound работа, которую нужно вынести за пределы потока запроса, чтобы не блокировать пайплайн. 📍 Навигация: ВакансииЗадачиСобесы 🐸 Библиотека шарписта #sharp_view

🤩 Подборка вакансий для шарпистов C# Backend Developer — от 180 000 ₽ гибрид в Санкт-Петербурге Unity разработчик — до 4 500 €, гибрид в Алматы Fullstack-разработчик (C# / React Native) — удалёнка или гибрид в Пензе ➡️ Еще больше топовых вакансий — в нашем канале C# Jobs 🐸 Библиотека шарписта

⚡️ Никаких больше var Microsoft официально объявила: в C# 15 ключевое слово var признаётся устаревшим. Команда языка ссылаетс
⚡️ Никаких больше var Microsoft официально объявила: в C# 15 ключевое слово var признаётся устаревшим. Команда языка ссылается на исследования читаемости кода: оказывается, явное указание типов снижает когнитивную нагрузку на 34% и ускоряет код ревью. Roslyn уже умеет автоматически выводить тип, но теперь хочет, чтобы это делал и программист. Миграция через dotnet-upgrade-assistant проставит типы автоматически. Но 40 000 строк кода всё равно ждут вас в ближайшем будущем. ➡️ Источник Попались? С первым апреля!😁 📍 Навигация: ВакансииЗадачиСобесы 🐸 Библиотека шарписта

🚩 OpenFeature для .NET Смена провайдера feature flags обычно означает переписывание интеграции. OpenFeature это открытый ста
🚩 OpenFeature для .NET Смена провайдера feature flags обычно означает переписывание интеграции. OpenFeature это открытый стандарт под крылом CNCF, который даёт единый vendor-agnostic API: меняете провайдера, меняете одну строчку, код не трогаете. Установка
dotnet add package OpenFeature
Требования: .NET 8+ или .NET Framework 4.6.2+ Минимальный пример:
await Api.Instance.SetProviderAsync(new InMemoryProvider());

var client = Api.Instance.GetClient();
bool v2Enabled = await client.GetBooleanValueAsync("v2_enabled", false);

if (v2Enabled)
{
    // новая логика
}
Флаги с контекстом Передавайте данные о пользователе/запросе для контекстно-зависимых решений:
// Глобально
EvaluationContext ctx = EvaluationContext.Builder()
    .Set("region", "us-east-1")
    .Build();
Api.Instance.SetContext(ctx);

// Или прямо в вызове
bool flagValue = await client.GetBooleanValueAsync(
    "some-flag", false, reqCtx);
Логика вокруг вычисления флага Добавляйте поведение на любом этапе: до, после, при ошибке, в любом случае.
// Глобально для всех вызовов
Api.Instance.AddHooks(new ExampleGlobalHook());

// Только для конкретного клиента
client.AddHooks(new ExampleClientHook());
Встроенный LoggingHook пишет детальные логи через Microsoft.Extensions.Logging. Реакция на изменения
Api.Instance.AddHandler(
    ProviderEventTypes.ProviderReady,
    (eventDetails) => Console.WriteLine(eventDetails.Type)
);
Подписывайтесь на ProviderReady, ProviderError, ProviderConfigurationChanged. Dependency Injection (экспериментально)
dotnet add package OpenFeature.Hosting
builder.Services.AddOpenFeature(featureBuilder => {
    featureBuilder
        .AddInMemoryProvider()
        .AddHook<LoggingHook>();
});
Поддержка domain-scoped провайдеров: разные провайдеры для разных частей приложения. Несколько провайдеров одновременно с разными стратегиями: - FirstMatchStrategy — первый ненулевой результат - FirstSuccessfulStrategy — первый успешный, игнорируя ошибки - ComparisonStrategy — параллельное выполнение + сравнение результатов
var multiProvider = new MultiProvider(providerEntries, new FirstMatchStrategy());
await Api.Instance.SetProviderAsync(multiProvider);
Собственный провайдер:
public class MyProvider : FeatureProvider
{
    public override Metadata GetMetadata() =>
        new Metadata("My Provider");

    public override Task<ResolutionDetails<bool>> ResolveBooleanValueAsync(
        string flagKey, bool defaultValue,
        EvaluationContext? context = null, ...)
    {
        // ваша логика
    }
    // + ResolveString, ResolveInteger, ResolveDouble, ResolveStructure
}
Для ASP.NET Core один раз настроили контекст на входе запроса, и он автоматически попадает во все вычисления флагов в рамках этого запроса:
Api.Instance.SetTransactionContextPropagator(
    new AsyncLocalTransactionContextPropagator());
📍 Навигация: ВакансииЗадачиСобесы 🐸 Библиотека шарписта #sharp_view

🚫 Span<T> и async несовместимы Span<T> это ref struct. А ref struct не может существовать в куче. Это не ограничение реализации, это гарантия безопасности по дизайну. Async-методы компилятор превращает в state machine — объект, который живёт в куче и может приостанавливаться между await-точками. Локальные переменные такого метода становятся полями этого объекта. Поле типа ref struct в объекте на куче — запрещено. Поэтому компилятор просто не даст использовать Span<T> в async-методе.
// Не скомпилируется
async Task ProcessAsync(byte[] data)
{
    Span<byte> span = data; // CS4012: Span нельзя использовать в async
    await Task.Delay(100);
    Process(span);
}
Что происходит под капотом Компилятор превращает async-метод примерно в это:
// Упрощённо — что генерирует компилятор
private struct ProcessAsyncStateMachine : IAsyncStateMachine
{
    public byte[] data;
    public Span<byte> span; // ← невозможно: ref struct не может быть полем
    public int _state;
    // ...
}
Стек фрейм между await не гарантирован, потому что поток может смениться, метод может возобновиться на другом потоке. Span на стеке к тому моменту уже не существует. Как работать с данными в async-коде Memory<T> — это то, для чего он и создан. Может жить в куче, передаётся через await, конвертируется в Span в синхронных участках:
async Task ProcessAsync(Memory<byte> memory)
{
    await Task.Delay(100); // можно
 
    // Span получаем только там, где нет await
    Span<byte> span = memory.Span;
    Process(span);
}
Паттерн: Memory<T> для хранения и передачи через async-границы, Span<T> для фактической работы с данными в синхронном контексте.
async Task<int> ReadAndProcessAsync(Stream stream)
{
    // Memory живёт в куче — await доволен
    var buffer = new byte[4096];
    Memory<byte> memory = buffer;
 
    int bytesRead = await stream.ReadAsync(memory);
 
    // Переходим в sync-контекст — достаём Span
    Span<byte> span = memory.Span[..bytesRead];
    return CountNewlines(span);
}
 
static int CountNewlines(Span<byte> data)
{
    int count = 0;
    foreach (var b in data)
        if (b == '\n') count++;
    return count;
}
Коротко Span<T> — инструмент для горячего пути в синхронном коде. Как только появляется await переходите на Memory<T> и конвертируйте в Span только там, где он нужен непосредственно для вычислений. 📍 Навигация: ВакансииЗадачиСобесы 🐸 Библиотека шарписта #il_люминатор

🎶 Разработчик написал программу для управления самодельным проигрывателем винила Разработчик с Reddit строит автоматический
🎶 Разработчик написал программу для управления самодельным проигрывателем винила Разработчик с Reddit строит автоматический проигрыватель пластинок с нуля: механику, электронику и прошивку для STM32. Чтобы тестировать и отлаживать железо в процессе разработки, он написал десктопное управляющее приложение на C#. Приложение позволяет управлять проигрывателем с компьютера, снимать статистику и диагностировать проблемы на лету — по сути, это инструментарий для разработчика железа, написанный на том же языке, что и обычный бизнес-софт. Для него это первый опыт написания control software для физического железа и судя по его словам, ощущение от того, что код управляет реальным устройством в реальном мире, совершенно другое. ➡️ Источник 📍 Навигация: ВакансииЗадачиСобесы 🐸 Библиотека шарписта #entry_point

💡 Красивые алгоритмы медленны при малом n Красивые алгоритмы с хорошей асимптотикой имеют большие константы. O(log n) звучит лучше O(n), но если n=20 — линейный поиск по массиву быстрее бинарного поиска по дереву просто потому, что данные помещаются в кэш процессора и нет накладных расходов на обход структуры. Допустим, нужно найти обработчик по типу события. Первый импульс это словарь или дерево:
// "Правильное" решение — O(1) lookup
private readonly Dictionary<string, IHandler> _handlers = new()
{
    ["OrderCreated"] = new OrderCreatedHandler(),
    ["OrderCancelled"] = new OrderCancelledHandler(),
    ["OrderShipped"] = new OrderShippedHandler(),
};

// "Наивное" решение — O(n) linear scan
private readonly (string EventType, IHandler Handler)[] _handlers =
[
    ("OrderCreated", new OrderCreatedHandler()),
    ("OrderCancelled", new OrderCancelledHandler()),
    ("OrderShipped", new OrderShippedHandler()),
];

public IHandler? Find(string eventType)
{
    foreach (var (type, handler) in _handlers)
        if (type == eventType) return handler;
    return null;
}
При 5–20 обработчиках линейный массив часто быстрее словаря: данные лежат последовательно в памяти, нет хеширования, нет разыменования указателей, кэш доволен. Dictionary начинает выигрывать при десятках тысяч элементов и только тогда. Бенчмарк говорит сам за себя:
[MemoryDiagnoser]
public class LookupBenchmark
{
    private readonly Dictionary<string, int> _dict;
    private readonly (string, int)[] _array;

    public LookupBenchmark()
    {
        var data = Enumerable.Range(0, 10)
            .Select(i => ($"key{i}", i))
            .ToArray();

        _dict = data.ToDictionary(x => x.Item1, x => x.Item2);
        _array = data;
    }

    [Benchmark(Baseline = true)]
    public int DictLookup() => _dict["key7"];

    [Benchmark]
    public int ArrayScan()
    {
        foreach (var (k, v) in _array)
            if (k == "key7") return v;
        return -1;
    }
}
При n=10 массив зачастую быстрее и не аллоцирует ничего лишнего. Измерьте сами. Когда измерения при реальной нагрузке показывают, что n действительно большой и растёт. Не раньше. Routing-таблица с 15 маршрутами, валидация с 8 правилами, матчинг по 12 паттернам — всё это «малый n», и простой цикл здесь выиграет у любого красивого решения. 📍 Навигация: ВакансииЗадачиСобесы 🐸 Библиотека шарписта #il_люминатор

👨‍💻 Разделяемое состояние в многопоточке Кажется, что примитивы атомарны. Это не так в смысле видимости между потоками: процессор и компилятор переупорядочивают инструкции, каждое ядро держит своё значение в кэше:
private bool _cacheLoaded;

// Поток A
_cacheLoaded = true;

// Поток B — может прочитать false, даже если A уже записал true
if (!_cacheLoaded) LoadCache(); // загружается дважды, данные затираются
Как это исправить 1. lock. Подходит для составных операций: «прочитать → изменить → записать» должны выполняться как одно целое:
private readonly object _sync = new object();
private int _count;

public void Increment()
{
    lock (_sync) { _count++; }
}
2. volatile. Запрещает кэширование значения в регистре. Не заменяет lock. Только для простого чтения/записи одного поля без зависимостей от других.
private int _count;

public void Increment()
{
    Interlocked.Increment(ref _count);
}
3. Interlocked. Атомарная операция на уровне процессора. Быстрее lock, но только для простых числовых операций:
private int _count;

public void Increment()
{
    Interlocked.Increment(ref _count);
}
📍 Навигация: ВакансииЗадачиСобесы 🐸 Библиотека шарписта #sharp_view

⚙️ Substring или Slice Substring и Slice выглядят похоже, но работают принципиально по-разному. Substring — это new string(...). Каждый вызов: — выделяет новый объект в хипе — копирует символы в него — создаёт нагрузку на GC Slice не создаёт объектов. Это просто новый указатель + длина поверх той же памяти. int.Parse(ReadOnlySpan<char>) читает символы напрямую оттуда. Частая ошибка
// Так делать не надо — убивает весь смысл
int id = int.Parse(span.Slice(5, 2).ToString());
ToString() на Span создаёт новую строку. Вернулись к исходной проблеме. Одно правило: если Substring перед Parse это кандидат на замену. 📍 Навигация: ВакансииЗадачиСобесы 🐸 Библиотека шарписта #il_люминатор

⚡️ Рестарт ради смены настроек это лишнее Если конфигурация меняется редко, перезапуск приложения — не проблема. Но когда нужно менять, например, тарифы или флаги в реальном времени, рестарт становится дорогим решением. IOptionsMonitor<T> позволяет получать актуальные значения сразу после изменения файла конфигурации. Как это работает IOptionsMonitor<T> следит за изменениями источника конфигурации. При каждом обращении к CurrentValue возвращается актуальное значение. Дополнительно можно подписаться на событие изменения через OnChange:
public class DynamicPricingService
{
    private readonly IOptionsMonitor<PricingOptions> _options;

    public DynamicPricingService(IOptionsMonitor<PricingOptions> options)
    {
        _options = options;

        _options.OnChange(updatedOptions =>
        {
            Log.Information("Pricing updated: BaseRate={BaseRate}",
                updatedOptions.BaseRate);
        });
    }

    public decimal CalculatePrice(decimal distance)
    {
        var currentOptions = _options.CurrentValue;

        return currentOptions.BaseRate + (distance * currentOptions.PerMileRate);
    }
}
Каждый вызов CalculatePrice берёт свежее значение из CurrentValue без рестарта и без ручного сброса кэша. Регистрация в Program.cs:
builder.Services.AddOptions<PricingOptions>()
    .BindConfiguration("Pricing", binderOptions =>
    {
        binderOptions.BindNonPublicProperties = false;
        binderOptions.ErrorOnUnknownConfiguration = true;
    })
    .ValidateDataAnnotations();
ErrorOnUnknownConfiguration = true защищает от опечаток в ключах — неизвестное поле в конфиге вызовет ошибку, а не тихо проигнорируется. IOptionsMonitor против IOptionsSnapshot IOptionsMonitor<T> — синглтон. Одно и то же значение живёт на протяжении всего времени работы приложения и обновляется при изменении файла. IOptionsSnapshot<T> — скоупед. Значение фиксируется один раз на запрос и не меняется до его завершения. Это важно там, где нужна консистентность внутри одного HTTP-запроса — чтобы один и тот же запрос не увидел разные значения конфигурации в начале и в конце обработки. Если сервис живёт в синглтоне, используйте IOptionsMonitor. Если важна согласованность в рамках запроса, IOptionsSnapshot. 📍 Навигация: ВакансииЗадачиСобесы 🐸 Библиотека шарписта #sharp_view

⚙️ Покрытие кода для .NET Coverlet — инструмент для измерения покрытия кода в .NET-проектах. Он работает на Windows, macOS и
⚙️ Покрытие кода для .NET Coverlet — инструмент для измерения покрытия кода в .NET-проектах. Он работает на Windows, macOS и Linux, поддерживает .NET Framework и .NET Core, и умеет считать покрытие по строкам, ветвям и методам. Без инструмента покрытия вы пишете тесты вслепую. Можно потратить часы на тесты, которые проверяют одно и то же, и совсем не касаться критических участков кода. Coverlet показывает точную картину: вот этот метод не вызывается ни одним тестом, а вот эта ветка if никогда не выполняется при тестировании. Как подключить Есть четыре варианта интеграции. Самый распространённый для современных проектов через VSTest. Он уже включён по умолчанию в шаблоны xUnit-проектов начиная с .NET 8. Если его нет, добавляем в тестовый проект:
dotnet add package coverlet.collector
Запускаем тесты с флагом сбора покрытия:
dotnet test --collect:"XPlat Code Coverage"
После выполнения в папке TestResults появится файл coverage.cobertura.xml с отчётом. Второй вариант через MSBuild:
dotnet add package coverlet.msbuild
dotnet test /p:CollectCoverage=true
Итог сразу появится в терминале, а файл coverage.json сохранится в корне тестового проекта. Третий вариант. Глобальный инструмент командной строки:
dotnet tool install --global coverlet.console
coverlet /path/to/test-assembly.dll --target "dotnet" --targetargs "test /path/to/test-project --no-build"
Четвёртый, самый новый, это интеграция с Microsoft Testing Platform. Подходит для проектов на Microsoft.Testing.Platform, требует .NET 8 и выше:
dotnet add package coverlet.MTP
dotnet test --coverlet
Если вы работаете в Visual Studio на Windows, расширение Fine Code Coverage умеет читать вывод Coverlet и подсвечивать покрытие прямо в редакторе. На macOS есть аналог VSMac-CodeCoverage. Coverlet не требует сложной настройки, встраивается в стандартный dotnet test, работает на всех платформах и бесплатен. ➡️ Репозиторий 📍 Навигация: ВакансииЗадачиСобесы 🐸 Библиотека шарписта #sharp_view

✏️ Задание: не положить базу после истечения TTL Представьте: TTL кэша истёк, и сотни запросов одновременно обнаружили пустой
✏️ Задание: не положить базу после истечения TTL Представьте: TTL кэша истёк, и сотни запросов одновременно обнаружили пустой кэш. Все ломятся в базу за одним и тем же значением. Это называется cache stampede. Как бы вы это решили? Какой примитив синхронизации выбрать, чтобы первый запрос шёл в БД, а остальные ждали его результата? Подумайте и проверьте свой ответ здесь: @csharp_interview_lib 📍 Навигация: ВакансииЗадачиСобесы 🐸 Библиотека шарписта #dotnet_challenge

😱 Если ваш продукт не умеет отдавать данные в формате, понятном AI-агенту, то вас просто не существует Скрипт не будет кликать по красивым кнопкам в браузере, он уйдёт к конкуренту с нормальным API. Перестроить архитектуру под машинных клиентов — это уже не хайп, а необходимое условие сохранения конкурентоспособности. Как адаптировать продукт и не исчезнуть из выдачи: — интегрировать MCP и A2A-взаимодействие, чтобы агенты могли вас читать; — научиться контролировать стоимость (лимиты, кэш, роутинг между моделями); — настроить AgentOps: трейсинг, логирование и отлов регрессий. Всё это ждёт вас на обновлённом курсе «Разработка AI-агентов». Мы специально сделали фокус на утилитарном инжиниринге и production-ready решениях. Кстати, до 29 марта можно забрать курс с большой скидкой, и стоит поторопиться — мест на потоке всё меньше. Зафиксировать цену и начать деплоить агентов без слива бюджета 👈

🤔 Запросы в БД быстрые, кэш есть, async везде, но приложение тормозит Скорее всего виноват не код, а аллокации. Каждый раз,
🤔 Запросы в БД быстрые, кэш есть, 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