Библиотека шарписта | C#, F#, .NET, ASP.NET
Все самое полезное для 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 872 名订阅者,在 技术与应用 类别中位列第 6 212,并在 俄罗斯 地区排名第 30 851 位。
📊 受众指标与增长动态
自 невідомо 创建以来,项目保持高速增长,吸引了 21 872 名订阅者。
根据 10 六月, 2026 的最新数据,频道保持稳定运转。过去 30 天订阅人数变化为 -87,过去 24 小时变化为 -4,整体触达仍然可观。
- 认证状态: 未认证
- 互动率 (ER): 平均受众互动率为 12.06%。内容发布后 24 小时内通常能获得 7.04% 的反应,占订阅者总量。
- 帖子覆盖: 每篇帖子平均可获得 2 638 次浏览,首日通常累积 1 540 次浏览。
- 互动与反馈: 受众积极参与,单帖平均反应数为 8。
- 主题关注点: 内容集中在 .net, шарписта, навигация, await, string 等核心主题上。
📝 描述与内容策略
作者将该频道定位为表达主观观点的平台:
“Все самое полезное для C#-разработчика в одном канале.
По рекламе: @proglib_adv
Учиться у нас: https://proglib.io/w/b60af5a4
Для обратной связи: @proglibrary_feeedback_bot
РКН: https://gosuslugi.ru/snet/67a5c81cdc130259d5b7fead”
凭借高频更新(最新数据采集于 11 六月, 2026),频道始终保持新鲜度与高覆盖。分析显示受众积极互动,使其成为 技术与应用 类别中的关键影响点。
JsonSerializerOptions в каждом запросе. Это уничтожает встроенный кеш метаданных System.Text.Json и превращает JSON сериализацию в дорогую операцию, которая повторяется сотни раз в секунду под нагрузкой.
Почему это так дорого
JsonSerializerOptions это не просто настройки. Это место, где System.Text.Json хранит кэшированные метаданные о том, как сериализовать и десериализовать типы.
Каждый новый экземпляр JsonSerializerOptions начинает с пустого кеша. System.Text.Json должна заново анализировать тип, строить информацию о сериализации, кешировать её. Потом запрос закончился и всё выбросилось.
Следующий запрос приходит. Новый экземпляр. Пустой кеш снова. Всё сначала.
Microsoft так серьёзно относится к этому, что добавили анализатор CA1869, который явно предупреждает: не создавайте JsonSerializerOptions локально в горячих путях.
Ошибка выглядит безобидно:
string ToJson(object value)
{
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web)
{
WriteIndented = false
};
return JsonSerializer.Serialize(value, options);
}
Под нагрузкой это выглядит как:
• CPU растёт без видимых причин
• Задержка становится нестабильной, p99 скачет
• Профилер показывает JSON сериализацию как горячую точку
• А вы не понимаете почему, если оптимизировали всё остальное
Создайте JsonSerializerOptions один раз при старте приложения и переиспользуйте везде:
public static class JsonDefaults
{
public static readonly JsonSerializerOptions Web = new(JsonSerializerDefaults.Web)
{
WriteIndented = false,
Converters = { new JsonStringEnumConverter() }
};
}
Используем кеш, никаких затрат
return JsonSerializer.Serialize(payload, JsonDefaults.Web);
Одна переменная, кеш остаётся тёплым, производительность стабильной.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#il_люминаторЧто такое readonly struct и чем он отличается от обычной структурыНа собесе часто ловят на вопросе «когда использовать». Многие говорят просто «для безопасности», но это неправильный ответ. Правильный ответ, который ждут на собесе ждёт только вас в нашем канале с вопросами с собесов 📍 Навигация: Вакансии • Задачи • Собесы 🐸 Библиотека шарписта #dotnet_challenge
Install-Package FreeSpire.PDFОграничение бесплатной версии — до 10 страниц на документ. Для личных задач или небольших проектов этого хватает. Базовый пример — одна страница:
PdfDocument doc = new PdfDocument();
doc.LoadFromFile("Sample.pdf");
PdfPageBase page = doc.Pages[1]; // вторая страница
PdfTextExtractor extractor = new PdfTextExtractor(page);
PdfTextExtractOptions options = new PdfTextExtractOptions { IsExtractAllText = true };
string text = extractor.ExtractText(options);
File.WriteAllText("output.txt", text);
doc.Close();
Весь документ сразу:
StringBuilder allText = new StringBuilder();
foreach (PdfPageBase page in doc.Pages)
{
var extractor = new PdfTextExtractor(page);
var options = new PdfTextExtractOptions { IsExtractAllText = true };
allText.AppendLine(extractor.ExtractText(options));
}
File.WriteAllText("output.txt", allText.ToString());
Что ещё умеет библиотека
Зашифрованные PDF открываются передачей пароля прямо в LoadFromFile. Если нужен текст только из конкретной области страницы — задаёшь прямоугольник через ExtractArea в точках (1 point = 1/72 дюйма).
Для таблиц есть отдельный PdfTableExtractor, который возвращает данные в виде массива. Сканы и нечитаемые PDF решаются через Spire.OCR в связке с основной библиотекой.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#sharp_viewpublic abstract class RepositoryBase<TEntity>
{
protected RepositoryBase(DbContext context)
{
Context = context;
ApplyEntityConfiguration(); // рефлексия
SubscribeToAuditEvents(); // подписка на события
}
}
Производный класс просто вызвал base() — и получил в нагрузку поведение, которое не запрашивал. Это невидимая связанность: поведение определяется не сигнатурой, а тем, что спрятано в цепочке наследования.
Особый случай — глобальное состояние
Статический инициализатор внутри базового класса меняет сериализацию для всего приложения при первой загрузке любого наследника. Не локально — глобально. Молча.
Наследование vs композиция
Глубокое дерево: RepositoryBase → MongoBase → AuditableBase → ProductRepository. Четыре уровня. Поведение на каждом. Ничего не видно на месте вызова.
Декоратор решает то же самое явно:
public class AuditingRepository : IProductRepository
{
private readonly IProductRepository _inner;
private readonly IAuditService _audit;
public async Task<Product> GetByIdAsync(Guid id)
{
await _audit.LogAccessAsync(id);
return await _inner.GetByIdAsync(id);
}
}
Каждый слой делает одну вещь. Зависимости видны. Убрать логирование можно не трогая репозиторий.
Глобальная конфигурация — в точку запуска
Если что-то влияет на всё приложение, оно должно жить в Program.cs, а не в базовом классе, который загружается неизвестно когда.
Базовый класс становится проблемой, когда берёт на себя слишком много. В этот момент он перестаёт быть абстракцией и превращается в объект бога.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#sharp_view// Так делать не надо
public class MySingleton
{
private readonly IScopedService _scoped;
public MySingleton(IScopedService scoped) // Проблема здесь
{
_scoped = scoped;
}
}
Синглтон захватывает Scoped-сервис при первом создании и держит его до конца жизни приложения. Scoped-сервис при этом теряет свой контекст, например DbContext, и начинает работать непредсказуемо.
Если нужен Scoped-сервис внутри синглтона, используйте IServiceScopeFactory:
public class MySingleton
{
private readonly IServiceScopeFactory _scopeFactory;
public MySingleton(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public void DoWork()
{
using var scope = _scopeFactory.CreateScope();
var scoped = scope.ServiceProvider.GetRequiredService<IScopedService>();
scoped.Execute();
}
}
И включите валидацию зависимостей в dev-окружении — ASP.NET поймает большинство проблем ещё при запуске.
builder.Host.UseDefaultServiceProvider(options =>
{
options.ValidateScopes = true;
options.ValidateOnBuild = true;
});
Если не уверены в выборе, начинайте со Scoped. Для веб-приложений это безопасный дефолт. Singleton используйте только тогда, когда явно нужно общее состояние, и убедитесь, что сервис действительно thread-safe. Transient подходит для простых утилит без состояния.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#sharp_viewProcessData(bool flag1, bool flag2);Явные типы — намерение читается сразу:
ProcessData(ProcessMode mode, ValidationOptions options);• Исключения или типы результата. Исключения созданы для исключительных ситуаций, а типы результата для ожидаемых ошибок. Смешивать их = заставлять пользователя угадывать, что пойдёт не так и в каком виде. • Минимум обязательных параметров. Чем больше параметров нужно передать для базового вызова, тем выше порог входа. Если для простого случая нужно заполнить пять аргументов, скорее всего интерфейс нуждается в умных значениях по умолчанию или отдельном упрощённом методе. Удобный интерфейс это уважение к тем, кто будет с ним работать. И к себе через полгода, когда придётся вернуться к этому коду. 📍 Навигация: Вакансии • Задачи • Собесы 🐸 Библиотека шарписта #il_люминатор
dotnet add package HttpClient.CurlDelegatingHandler --version 1.0.0-alpha.1Как использовать Передаёте
CurlDelegatingHandler при создании клиента. Чтобы запрос не ушёл в сеть, добавляете заголовок CanSend: False. Curl-команда придёт обратно в заголовке outputCurl.
using System.Text;
using CurlGenerator;
string url = "https://jsonplaceholder.typicode.com/posts";
string jsonPayload = @"{""title"": ""New Post"", ""body"": ""This is the body"", ""userId"": 1}";
var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
var httpClient = new HttpClient(new CurlDelegatingHandler());
httpClient.DefaultRequestHeaders.Add(Settings.CanSend, "False");
var result = await httpClient.PostAsync(url, content);
string outputCurl = result.Headers.GetValues(Settings.OutputCurl).FirstOrDefault();
Console.WriteLine(outputCurl);
Вывод:
curl -X POST 'https://jsonplaceholder.typicode.com/posts' -H 'Content-Type: application/json' ...
DelegatingHandler встраивается в цепочку обработчиков HttpClient. Вместо того чтобы отправлять запрос по сети, он его перехватывает, строит из него curl-строку и возвращает её в заголовке ответа. Вся логика изолирована в одном месте.
➡️ Затестить пакет
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#sharp_viewIDisposable и его асинхронный вариант IAsyncDisposable. Если объект реализует один из них, значит он держит что-то важное: файловый дескриптор, сетевое соединение, неуправляемую память.
Без using объект останется жить до следующего прохода GC, а ресурс под ним — ещё дольше:
// Утечка: поток не закроется при исключении
var stream = new FileStream(path, FileMode.Open);
// Правильно: using гарантирует закрытие даже при исключении
using var stream = new FileStream(path, FileMode.Open);
Для асинхронных ресурсов — await using:
await using var resource = GetAsyncDisposable();
Что проверить в коде
Потоки. Каждый Stream, StreamReader, StreamWriter должен быть обёрнут в using. Без этого файловые дескрипторы накапливаются.
HttpClient. Один из самых частых источников проблем. Его нельзя создавать на каждый запрос, так как это исчерпывает пул сокетов.
Крупные аллокации. Массивы, буферы, большие строки — если они создаются в цикле, это быстро давит на GC. Здесь помогает ArrayPool<T> или System.Buffers.
var pool = ArrayPool<byte>.Shared;
byte[] buffer = pool.Rent(4096);
try
{
// работа с буфером
}
finally
{
pool.Return(buffer);
}
Object Pooling. Если объекты дорогие в создании и используются часто — ObjectPool<T> из Microsoft.Extensions.ObjectPool снижает нагрузку на аллокатор.
Простое правило: если тип реализует IDisposable, оборачиваем его в using. Остальное — профилировщик покажет.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#il_люминаторInformation($"Building {project} v{version}");
Verbose(log => log($"Processing {items.Count} items in {stopwatch.ElapsedMilliseconds} ms"));
Cake.Sdk получил поддержку in-process NuGet клиента. Теперь через #tool и InstallTool можно устанавливать любые NuGet пакеты прямо в инструменты.
Добавлена поддержка .slnx файлов. DotNetTest и парсинг решений теперь распознают XML-формат solution файлов, а автоопределение типа пути включает .slnx.
Из других изменений: NuGetPack теперь поддерживает поле ReadMe для readme пакета, в AssemblyInfo creator можно добавлять несколько экземпляров одного атрибута, in-process NuGet restore заработал с аутентификацией через приватные фиды.
Есть breaking change для GitLab CI — поля CI_PIPELINE_ID и CI_BUILD_ID сменили тип с int на long, так как на gitlab.com идентификаторы давно вышли за пределы 32 бит.
➡️ Release Notes
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#async_newslogger.LogInformation($"User {userId} logged in at {time}");
Компилятор разворачивает это примерно в:
logger.LogInformation(string.Format("User {0} logged in at {1}", userId, time));
Строка формируется до вызова метода. Если уровень логирования Information отключён, строка всё равно создаётся, занимает память и тут же выбрасывается сборщиком мусора.
Структурное логирование решает проблему:
logger.LogInformation("User {UserId} logged in at {Time}", userId, time);
Здесь строка — это шаблон. Аргументы передаются отдельно. Логгер сначала проверяет, активен ли уровень, и только потом форматирует сообщение. Если уровень выключен — аллокации нет вообще.
Бонус: структурное логирование позволяет индексировать поля в системах вроде Seq, Elasticsearch, Datadog — вы сможете искать по UserId как по полю, а не парсить текст.
Начиная с .NET 6 есть ещё лучший вариант через compile-time source generators:
// Генерирует оптимальный код на этапе компиляции:
[LoggerMessage(Level = LogLevel.Information, Message = "User {UserId} logged in at {Time}")]
partial void LogUserLogin(int userId, DateTime time);
Никаких аллокаций, никакого боксинга, максимальная производительность — и всё это без изменения читаемости кода.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#il_люминатор
现已上线!2025 年 Telegram 研究 — 年度关键洞察 
