Библиотека шарписта | C#, F#, .NET, ASP.NET
Все самое полезное для C#-разработчика в одном канале. Наши курсы: https://clc.to/y3LDtw По рекламе: @proglib_adv Для обратной связи: @proglibrary_feeedback_bot РКН: https://gosuslugi.ru/snet/67a5c81cdc130259d5b7fead
Ko'proq ko'rsatish📈 Telegram kanali Библиотека шарписта | C#, F#, .NET, ASP.NET analitikasi
Библиотека шарписта | C#, F#, .NET, ASP.NET (@csharpproglib) Rus til segmentidagi kanali faol ishtirokchi. Hozirda hamjamiyat 21 703 obunachidan iborat bo'lib, Texnologiyalar & Aralashmalar toifasida 5 962-o'rinni va Rossiya mintaqasida 30 301-o'rinni egallagan.
📊 Auditoriya ko‘rsatkichlari va dinamika
невідомо sanasidan buyon loyiha tez o‘sib, 21 703 obunachiga ega bo‘ldi.
27 Avgust, 2026 dagi oxirgi ma’lumotlarga ko‘ra kanal barqaror faollikka ega. Oxirgi 30 kunda obunachilar soni -42 ga, so‘nggi 24 soatda esa 3 ga o‘zgardi va umumiy qamrov yuqori darajada qolmoqda.
- Tasdiqlash holati: Tasdiqlanmagan
- Jalb etish (ER): Auditoriya o‘rtacha 15.91% darajada jalb etiladi. Nashrdan keyingi dastlabki 24 soatda kontent odatda umumiy obunachilar sonining 7.92% ini tashkil etuvchi reaksiyalarni to‘playdi.
- Post qamrovi: Har bir post o‘rtacha 3 454 marta ko‘riladi; birinchi sutkada odatda 1 718 ta ko‘rish yig‘iladi.
- Reaksiyalar va o‘zaro ta’sir: Auditoriya faol: har bir postga o‘rtacha 26 ta reaksiya keladi.
- Tematik yo‘nalishlar: Kontent .net, шарписта, навигация, await, string kabi asosiy mavzularga jamlangan.
📝 Tavsif va kontent siyosati
Muallif resursni shaxsiy fikrni ifoda etish maydoni sifatida ta’riflaydi:
“Все самое полезное для C#-разработчика в одном канале.
Наши курсы: https://clc.to/y3LDtw
По рекламе: @proglib_adv
Для обратной связи: @proglibrary_feeedback_bot
РКН: https://gosuslugi.ru/snet/67a5c81cdc130259d5b7fead”
Yuqori yangilanish chastotasi (oxirgi ma’lumot 28 Avgust, 2026 da olingan) sababli kanal doimo dolzarb va katta qamrovli bo‘lib qoladi. Analitika auditoriya kontent bilan faol hamkorlik qilishini, uni Texnologiyalar & Aralashmalar toifasidagi muhim ta’sir nuqtasiga aylantirishini ko‘rsatadi.
Volatile.
Как это работает
Volatile.Read и Volatile.Write расставляют барьеры памяти в нужных местах:
• запись до Volatile.Write не может быть перенесена после неё
• чтение после Volatile.Read не может быть перенесено до него
На практике это значит: когда один поток устанавливает флаг, другие потоки рано или поздно увидят актуальное значение:
private int _flag;
public void Set()
{
Volatile.Write(ref _flag, 1);
}
public bool IsSet()
{
return Volatile.Read(ref _flag) == 1;
}
Это легче, чем lock
Volatile не захватывает монитор и не блокирует поток. Это просто барьер памяти. Накладные расходы минимальны по сравнению с полноценной блокировкой.
Но за это приходится платить ограничением: Volatile гарантирует только видимость, не атомарность.
Где это работает корректно
Типичный сценарий это флаг инициализации, который устанавливается один раз:
if (Volatile.Read(ref _initialized) == 0)
{
Initialize();
Volatile.Write(ref _initialized, 1);
}
Если этот код выполняется только в одном потоке, всё в порядке. Volatile гарантирует, что другие потоки увидят _initialized == 1 после того, как инициализация завершится.
Где это не работает
Если несколько потоков могут одновременно войти в этот блок, код становится небезопасным. Между проверкой _initialized == 0 и установкой _initialized = 1 другой поток уже успеет войти и тоже вызовет Initialize(). Здесь нужен Interlocked или полноценный lock.
Volatile подходит, когда один поток пишет, остальные читают. Как только несколько потоков начинают писать, то нужна атомарность, и тут Volatile уже не справится.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#il_люминаторSingleton → может зависеть от → Singleton Scoped → может зависеть от → Singleton, Scoped Transient → может зависеть от → Singleton, Scoped, Transient📍 Навигация: Вакансии • Задачи • Собесы 🐸 Библиотека шарписта #sharp_view
TaskCompletionSource<T> решает одну конкретную проблему: вы хотите вернуть Task, но не можете использовать async/await, потому что результат приходит через коллбэк, событие или другой внешний сигнал.
Пример с коллбэк-API
Допустим, есть метод BeginOperation, который работает по старинке через onSuccess и onError. Оборачиваем его в нормальный Task:
public Task<string> GetDataAsync()
{
var tcs = new TaskCompletionSource<string>(
TaskCreationOptions.RunContinuationsAsynchronously);
BeginOperation(
onSuccess: result => tcs.SetResult(result),
onError: ex => tcs.SetException(ex));
return tcs.Task;
}
Теперь вызывающий код просто пишет await GetDataAsync() и не знает, что внутри коллбэки.
Почему важен RunContinuationsAsynchronously
Это не просто флаг для галочки. Без него продолжения выполняются прямо на том потоке, который вызвал SetResult. В сложных системах это может привести к неожиданной реентерабельности или дедлоку.
С флагом продолжения уходят в пул потоков и поведение становится предсказуемым.
Координация сигналов
Настоящая сила TaskCompletionSource проявляется, когда нужно синхронизировать несколько частей системы:
public Task WaitForSignalAsync()
{
return _signalTcs.Task;
}
public void Signal()
{
_signalTcs.TrySetResult();
}
Один компонент ждёт сигнала через await WaitForSignalAsync(), другой вызывает Signal() когда готов. Это базовый паттерн для кастомных async-локов, очередей и event-систем.
Когда использовать
Подходит, если нужно обернуть коллбэк-API в Task, реализовать собственный примитив синхронизации, или управлять завершением задачи вручную из внешнего кода. Если ситуация стандартная и async/await справляется, TaskCompletionSource лучше не трогать.
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#sharp_view# ядро dotnet add package Moongazing.Veil # HTTP middleware dotnet add package Moongazing.Veil.AspNetCore # structured logs dotnet add package Moongazing.Veil.SerilogСлой 1. Строки и объекты Авто-обнаружение типа данных: email, телефон, карта, IBAN, JWT, IP, API-ключ и маскировка без конфига:
Veil.Mask("john.doe@gmail.com");
// j******e@g****.com
Veil.Mask("5425123456789012");
// 5425 **** **** 9012
Veil.Mask("Bearer eyJhbGci...");
// Bearer eyJh***...
Для DTO атрибут [Veiled]:
public class CustomerDto
{
public string Name { get; set; }
[Veiled]
public string Email { get; set; }
[Veiled(Show = 4, Position = VeilPosition.Last)]
public string CardNumber { get; set; }
}
var masked = Veil.MaskObject(original); // original не трогается
Слой 2. HTTP-трафик:
app.UseVeilRedaction(); // middleware в pipeline
// конфиг:
http.RedactHeaders("Authorization", "X-Api-Key");
http.RedactBodyFields("$.password", "$.creditCard");
http.RedactQueryParams("token", "api_key");
Данные редактируются до того, как попадают в логи.
Слой 3. Serilog без изменений в коде:
Log.Logger = new LoggerConfiguration()
.Destructure.WithVeil()
.Enrich.WithVeilRedaction()
.WriteTo.Console()
.CreateLogger();
Все существующие Log.Information("User {@User}", user) начинают маскировать данные автоматически. Ни один вызов переписывать не нужно.
➡️ NuGet
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#async_newsdotnet add package Moongazing.Veil # ядро dotnet add package Moongazing.Veil.AspNetCore # HTTP middleware dotnet add package Moongazing.Veil.Serilog # structured logsСлой 1. Строки и объекты ➡️ 📍 Навигация: Вакансии • Задачи • Собесы 🐸 Библиотека шарписта #async_news
IMemoryCache для одного узла, либо Redis для распределённых сценариев. А если нужно и то, и другое одновременно, с нормальной устойчивостью к сбоям, то приходится писать обёртки самим.
FusionCache это гибридный кэш для .NET с открытым исходным кодом. Он работает как двухуровневый кэш: L1 в памяти и L2 в распределённом хранилище. Переключение между режимами прозрачно, поэтому код менять не нужно.
Минимальный пример без DI:
var cache = new FusionCache(new FusionCacheOptions());
var product = cache.GetOrSet<Product>(
$"product:{id}",
_ => GetProductFromDb(id),
TimeSpan.FromSeconds(30)
);
С DI и настройками устойчивости:
services.AddFusionCache()
.WithDefaultEntryOptions(new FusionCacheEntryOptions()
.SetDuration(TimeSpan.FromMinutes(2))
.SetPriority(CacheItemPriority.High)
.SetFailSafe(true, TimeSpan.FromHours(2))
.SetFactoryTimeouts(
TimeSpan.FromMilliseconds(100), // soft timeout
TimeSpan.FromSeconds(2) // hard timeout
)
);
Fail-safe здесь означает: если фабрика данных упала или превысила таймаут, кэш вернёт устаревшее значение вместо ошибки на срок до двух часов.
Три сценария, с которыми сталкиваются почти все
1. Cache Stampede. Когда ключ протухает, сотни одновременных запросов идут прямо в базу. FusionCache блокирует параллельные вычисления и делает один запрос вместо ста.
2. Медленная фабрика данных. Если база или внешний сервис тормозит, запросы накапливаются. FusionCache поддерживает soft и hard таймауты: при soft таймауте возвращается устаревшее значение (если оно есть), при hard таймауте бросается исключение, и вы сами решаете, что делать.
3. Согласованность в кластере. Если у вас несколько нод, их L1-кэши могут разойтись. Backplane, например, через Redis Pub/Sub, уведомляет все ноды об изменениях.
FusionCache также совместим с HybridCache от Microsoft и может использоваться как его реализация.
➡️ Репозиторий
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#sharp_viewpublic union OneOrMore<T>(T, IEnumerable<T>)
{
public IEnumerable<T> AsEnumerable() => Value switch
{
T single => [single],
IEnumerable<T> multiple => multiple,
null => []
};
}
Использование:
OneOrMore<string> tags = "dotnet";
OneOrMore<string> moreTags = new[] { "csharp", "unions", "preview" };
foreach (var tag in tags.AsEnumerable())
Console.Write($"[{tag}] ");
// [dotnet]
Поддержка UnionAttribute и IUnion в рантайме ещё не добавлена, поэтому в Preview 2 нужно вручную добавить в проект небольшой полифил:
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct,
AllowMultiple = false)]
public sealed class UnionAttribute : Attribute;
public interface IUnion
{
object? Value { get; }
}
}
После этого синтаксис union работает в полном объёме.
➡️ Блог разработчиков
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#il_люминаторUnion-тип объявляет закрытое множество допустимых типов. Компилятор знает полный список, поэтому проверяет исчерпываемость switch-выражений прямо при сборке.
Простейший пример:
public record class Cat(string Name);
public record class Dog(string Name);
public record class Bird(string Name);
public union Pet(Cat, Dog, Bird);
Переменная Pet хранит ровно один из трёх типов. Присваивание работает через неявное преобразование:
Pet pet = new Dog("Rex");
Console.WriteLine(pet.Value); // Dog { Name = Rex }
switch по такой переменной не требует ветки default или _. Если вы позже добавите четвёртый тип в объявление union, компилятор выдаст предупреждение в каждом месте, где не хватает обработчика.
string name = pet switch
{
Dog d => d.Name,
Cat c => c.Name,
Bird b => b.Name,
};
Фича доступна начиная с .NET 11 Preview 2.
➡️ Блог разработчиков
📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека шарписта
#il_люминаторlabel.Text или textBox.Value, получаем InvalidOperationException. В WPF для этого используется Dispatcher.BeginInvoke, в WinForms — Control.Invoke или Control.BeginInvoke.
Казалось бы, очевидное правило. Но баг всё равно появляется: чаще всего тогда, когда разработчик делает Task.Run, внутри него обращается к UI, а анализатор это не видит.
С Dispatcher.Invoke другая история. Это синхронный вызов, он блокирует текущий поток до завершения. Если вызвать Invoke из самого UI-потока или из кода, который UI-поток уже ждёт, получаем дедлок. Правило простое: почти всегда нужен BeginInvoke (асинхронный), а не Invoke.
Как находить такие баги до прода
Статические анализаторы: Roslyn, AsyncFixer, Microsoft.VisualStudio.Threading.Analyzers и ThreadSafetyAnalyzer умеют находить прямые обращения к UI из неправильного потока, синхронные блокировки async-методов и потенциальные дедлоки в диспетчере.
Подключить их можно через NuGet:
dotnet add package Microsoft.VisualStudio.Threading.Analyzers dotnet add package AsyncFixerПосле подключения анализаторы начинают предупреждать прямо в IDE, до сборки. Часть правил даже предлагает автофикс. На практике это работает: после включения анализаторов в одном проекте удалось найти три гонки данных ещё до того, как они добрались до пользователей. 📍 Навигация: Вакансии • Задачи • Собесы 🐸 Библиотека шарписта #sharp_view
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_люминаторLangGraph, извлечение данных из кривых сканов для RAG и комплаенс по 152-ФЗ.
Если всё ещё сомневаетесь, послушайте голосовое от спикера курса Влада Прошинского, где он объясняет, как правильно тестировать агентов перед релизом.Программа курса, полный состав спикеров и другие подробности 👈🏻 ВАЖНО! До 5 апреля на курс действует скидка, но свободные места могут закончиться раньше.
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_viewawait 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