Библиотека шарписта | C#, F#, .NET, ASP.NET
Все самое полезное для C#-разработчика в одном канале. Наши курсы: https://clc.to/y3LDtw По рекламе: @proglib_adv Для обратной связи: @proglibrary_feeedback_bot РКН: https://gosuslugi.ru/snet/67a5c81cdc130259d5b7fead
Больше📈 Аналитический обзор Telegram-канала Библиотека шарписта | C#, F#, .NET, ASP.NET
Канал Библиотека шарписта | C#, F#, .NET, ASP.NET (@csharpproglib) языкового сегмента Русский является активным участником. Сейчас сообщество объединяет 21 707 подписчиков, занимая 5 962 место в категории Технологии и приложения и 30 301 место в регионе Россия.
📊 Показатели аудитории и динамика
С момента создания невідомо проект демонстрирует стремительный рост, собрав аудиторию из 21 707 подписчиков.
Согласно последним данным от 27 августа, 2026, канал показывает стабильную активность. За последние 30 дней изменение числа участников составило -42, а за последние 24 часа — 3, при этом общий охват остаётся высоким.
- Статус верификации: Не верифицирован
- Уровень вовлечённости (ER): Средний показатель вовлечённости аудитории составляет 15.91%. В первые 24 часа после публикации контент обычно набирает 7.92% реакций от общего числа подписчиков.
- Охват публикаций: В среднем каждый пост получает 3 454 просмотров. В течение первых суток публикация набирает 1 718 просмотров.
- Реакции и взаимодействия: Аудитория активно поддерживает контент: среднее количество реакций на один пост — 26.
- Тематические интересы: Контент сосредоточен на ключевых темах, таких как .net, шарписта, навигация, await, string.
📝 Описание и контентная политика
Автор описывает ресурс как площадку для выражения субъективного мнения:
“Все самое полезное для C#-разработчика в одном канале.
Наши курсы: https://clc.to/y3LDtw
По рекламе: @proglib_adv
Для обратной связи: @proglibrary_feeedback_bot
РКН: https://gosuslugi.ru/snet/67a5c81cdc130259d5b7fead”
Благодаря высокой частоте обновлений (последние данные получены 28 августа, 2026) канал поддерживает актуальность и высокий уровень охвата публикаций. Аналитика показывает, что аудитория активно взаимодействует с контентом, что делает его важной точкой влияния в категории Технологии и приложения.
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_люминаторOrdinal — побайтовое сравнение Unicode-значений. Быстрое, предсказуемое, не зависит от культуры.
CurrentCulture — сравнение по правилам текущей локали операционной системы. Медленнее, результат может отличаться на разных машинах.
Пример с немецким языком:
string a = "straße"; // немецкое "улица"
string b = "strasse"; // то же слово, другое написание
a.Equals(b, StringComparison.CurrentCultureIgnoreCase); // true на de-DE
a.Equals(b, StringComparison.CurrentCultureIgnoreCase); // false на en-US
a.Equals(b, StringComparison.OrdinalIgnoreCase); // false везде
Один и тот же код даёт разные результаты в зависимости от того, где запущен. На машине разработчика работает, на продакшен-сервере — нет.
Турецкая проблема:
// В турецкой локали (tr-TR): "I".ToLower() == "ı" // не "i", а "ı" (без точки) "i".ToUpper() == "İ" // не "I", а "İ" (с точкой) // Поэтому: string input = "FILE"; input.ToLower() == "file" // false на турецкой машине!Именно из-за этого Microsoft SQL Server годами имел баги в коде, который работал везде, кроме турецких инсталляций. Анализатор CA1307 в .NET предупреждает о вызовах сравнения без явного указания StringComparison. Включите его — он поймает большинство таких мест автоматически. 📍 Навигация: Вакансии • Задачи • Собесы 🐸 Библиотека шарписта #il_люминатор
string s = "😊";
Console.WriteLine(s.Length); // 2, не 1
Length считает не символы, а UTF-16 кодовые единицы. Эмодзи и многие Unicode-символы занимают две единицы — суррогатную пару.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#sharp_view