Библиотека шарписта | 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 703 名订阅者,在 技术与应用 类别中位列第 5 962,并在 俄罗斯 地区排名第 30 301 位。
📊 受众指标与增长动态
自 невідомо 创建以来,项目保持高速增长,吸引了 21 703 名订阅者。
根据 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),频道始终保持新鲜度与高覆盖。分析显示受众积极互动,使其成为 技术与应用 类别中的关键影响点。
_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 выбрасывает исключение при запуске, если конфигурация невалидна. Не в рантайме, не при первом запросе — сразу при старте.
Чистое внедрение через extension method
Чтобы не засорять 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;
}
}
В итоге конфигурация живёт в одном месте и имеет чёткую структуру. Все ключи типизированы и никаких магических строк.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#sharp_viewdotnet add package MongoDB.DriverДелаете кастомный атрибут, чтобы сущность сама знала свою коллекцию:
[CollectionName("orders")]
public class Order
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public string CustomerName { get; set; }
}
Generic-репозиторий читает атрибут сам и никаких магических строк в Program.cs:
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);
}
}
Регистрируете один раз через и больше никогда не трогаете Program.cs при добавлении новых коллекций:
services.AddScoped(typeof(IMongoRepository<>), typeof(MongoRepository<>));
Новая коллекция? Просто новый класс с [CollectionName].
А для сложных запросов Builders<T>:
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();
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#sharp_viewvar 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В appsettings.Production.json пишем только заглушки, никаких реальных значений:
{
"Database": {
"ConnectionString": "CONFIGURED_IN_SECRETS_MANAGER"
},
"PaymentGateway": {
"ApiKey": "CONFIGURED_IN_SECRETS_MANAGER"
}
}
Можно спрятать секреты в GitLab CI, например:
# .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
По итогу в репозитории будут только безопасные дефолтные значения, а все секреты вынесены.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#sharp_viewChannel<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_люминатор
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_viewreturn Ok(list)?
Правильно — всё копится в памяти. Сервер ждёт, пока соберётся весь ответ, только потом отправляет клиенту. При высокой нагрузке это убивает и память, и растёт задержка.
Возникает вопрос:
Как не держать весь ответ в памяти и какими инструментами воспользоваться?Данные можно отдавать данные по мере готовности, без лишних аллокаций и с контролем над потоком. ➡️ Ответ в библиотеке собеса по C# 📍 Навигация: Вакансии • Задачи • Собесы 🐸 Библиотека шарписта #dotnet_challenge
pr-review skill — четырёхфазный процесс: анализ issue, проверка наличия тестов, до четырёх попыток фикса разными моделями, финальный отчёт с объяснением выбора лучшего подхода.
write-tests-agent — анализирует issue и выбирает нужный тип тестов: UI-тесты через Appium или XAML-тесты. Обязательно проверяет, что тест падает без фикса и проходит с ним.
sandbox-agent — ручная валидация в Sandbox-приложении. Для сценариев, которые трудно автоматизировать: визуальные баги, сложные жесты, поворот экрана.
learn-from-pr agent — анализирует смёрженные PR и улучшает инструкции и документацию. Система становится умнее после каждого цикла.
Для поиска фикса используются четыре модели последовательно — каждая предлагает независимый подход, тестирует его, записывает результат. После первого раунда модели видят чужие попытки и предлагают новые идеи. Одновременный доступ к одним файлам и одному устройству всё сломает.
➡️ Блог разработчиков
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#пульс_индустрииtime-travel дебаг в LangGraph и уметь роутить запросы на лету. Всё это мы учли в обновлённом курсе по разработке AI-агентов, где акцент сделан именно на AgentOps и жёсткий контроль ресурсов.
Также в программе:
— оценка качества, трейсинг и защита от деградации пайплайнов;
— мультиагентные паттерны и интеграция по протоколу MCP;
— локальный деплой Open Source под 152-ФЗ (когда данные нельзя выносить наружу).
Кажется, это единственный адекватный roadmap по переходу от блокнотов к enterprise-решениям.
Прямо сейчас можно урвать курс с увесистой скидкой (49 000 ₽ 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
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_люминатор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_viewpartition делит коллекцию на две части, но обе остаются одного типа. Если нужно одновременно отфильтровать и преобразовать элементы — приходилось делать два прохода или писать свою функцию.
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