Библиотека шарписта | C#, F#, .NET, ASP.NET
21.8K subscribers
2.87K photos
44 videos
91 files
5.31K links
Все самое полезное для C#-разработчика в одном канале.

Наши курсы: https://clc.to/y3LDtw

По рекламе: @proglib_adv

Для обратной связи: @proglibrary_feeedback_bot

РКН: https://gosuslugi.ru/snet/67a5c81cdc130259d5b7fead
Download Telegram
🆚 EF Core vs Dapper — новые фичи EF закрывают разрыв

EF Core и Dapper решают пересекающиеся, но не одинаковые задачи. С каждым релизом EF Core сокращает отставание по производительности, но у Dapper по-прежнему есть своя ниша.

Что нового в EF Core

Compiled queries убирают overhead на трансляцию повторяющихся запросов.
private static readonly Func<AppDb, Guid, Task<UserDto?>> GetById =
EF.CompileAsyncQuery((AppDb db, Guid id) =>
db.Users.AsNoTracking()
.Where(u => u.Id == id)
.Select(u => new UserDto(u.Id, u.Email))
.FirstOrDefault());


ExecuteUpdateAsync / ExecuteDeleteAsync позволяют делать set-based операции без материализации сущностей.
await db.Orders
.Where(o => o.Status == "Pending" && o.CreatedAtUtc < cutoff)
.ExecuteUpdateAsync(s =>
s.SetProperty(o => o.Status, _ => "Expired"));


Улучшенная поддержка raw SQL с проекцией в DTO, JSON-колонки, батчинг. В итоге для типичных API-ридов с небольшими DTO разница с Dapper составляет около 10–30%, а при доминировании latency на стороне БД она вообще не заметна.

Где Dapper всё ещё выигрывает

Кастомные джоины, CTE, оконные функции, db-specific хинты. Read-mostly микросервисы без change tracking. Хранимые процедуры. Минимальный jitter на p95–p99.

Прагматичный подход

Большинство команд используют гибрид. EF Core по умолчанию для всего с Select + AsNoTracking. Для топ 2–5 горячих запросов пробуют compiled queries. Если мало, реализуют только их через Dapper за интерфейсом (IOrderQueries), а остальное на EF.

Типичные ошибки

Возврат сущностей вместо DTO из API. Lazy loading без контроля. N+1 из-за навигационных свойств. Отсутствие AsNoTracking на ридах. Generic repository, который прячет фичи EF.

Compiled EF vs non-compiled экономит 0.5–2.5 мс на вызов. Если compiled query укладывается в SLO в пределах 15% от Dapper и проще в поддержке, оставляйте EF.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
7
📎 Расширение для Visual Studio, которое добавляет все недостающие using

Открываете C# файл, а там десяток красных подчёркиваний. Нажимаете Ctrl+., выбираете нужный namespace, переходите к следующей ошибке, снова Ctrl+.. И так по кругу. Visual Studio умеет добавлять using только по одному. В Rider есть Import Missing References, но это другая IDE.

OhUsings решает эту проблему внутри Visual Studio. Расширение сканирует файл, находит все неразрешённые типы через Roslyn и добавляет нужные using директивы разом. Без ручного перебора.

Как это работает

Под капотом OhUsings использует семантическую модель Roslyn, а не regex. Расширение читает диагностики компилятора (CS0246, CS0103, CS0234 и ещё около десятка), извлекает имена типов и находит подходящие namespace через SymbolFinder. Если тип однозначный, using добавляется автоматически. Если тип встречается в нескольких namespace (например, Timer живёт и в System.Timers, и в System.Threading), расширение покажет диалог выбора.

Добавленные директивы сортируются по алфавиту, System.* ставятся первыми. Всё форматируется через Roslyn.

Три области применения

Можно запустить на текущем файле, на активном проекте или на всём solution целиком.

Установка

Самый простой способ: в Visual Studio 2022 откройте Extensions → Manage Extensions, найдите OhUsings и нажмите Download. После перезапуска расширение готово к работе.

Или соберите из исходников:
git clone https://github.com/MabroukMahdhi/OhUsings.git

Откройте OhUsings.sln в Visual Studio 2022, соберите проект и установите полученный OhUsings.vsix.

Как использовать

Три варианта на выбор. Через меню: Tools → OhUsings: Import All Missing Usings. Через контекстное меню: правый клик в редакторе → OhUsings: Import All Missing Usings. Или через light bulb: ставите курсор на неразрешённый тип, нажимаете Ctrl+. и выбираете действие из предложенных OhUsings.

Результат отображается в статусной строке и Output Window.

Настройки

Расширение поддерживает несколько опций. SortUsings (по умолчанию true) отвечает за сортировку директив. PlaceSystemFirst (по умолчанию true) ставит System.* выше остальных. MaxDiagnosticsPerDocument (по умолчанию 200) ограничивает число обрабатываемых диагностик на файл для защиты производительности.

Ограничения

OhUsings работает только с типами из уже подключённых сборок. Добавлять NuGet пакеты или ссылки на проекты расширение пока не умеет, но это есть в планах. Если тип неоднозначный, его придётся выбрать вручную через диалог.

Итого

OhUsings экономит время на рутине. Вместо того чтобы обходить каждую ошибку по отдельности, вы запускаете одну команду и получаете все нужные using сразу. Расширение бесплатное, с открытым исходным кодом под MIT лицензией, без телеметрии и сетевых запросов.

➡️ Репозиторий

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
11🥱3
🔍 Thread.Start в C# — что происходит после вызова

Thread.Start существует с первой версии .NET, и большинство разработчиков знают, что он «запускает поток». Но что именно происходит в момент вызова, как передать данные в поток и почему Thread почти не используют в новом коде — стоит разобрать подробно.

Как это устроено

Thread — это обёртка над потоком операционной системы. Каждый созданный объект System.Threading.Thread соответствует одному OS-потоку со своим стеком (по умолчанию 1 МБ на x64).

Создать поток можно двумя способами — через ThreadStart или ParameterizedThreadStart:
// Без параметра
var t1 = new Thread(new ThreadStart(DoWork));

// С параметром типа object
var t2 = new Thread(new ParameterizedThreadStart(DoWorkWithParam));


После new Thread(...) поток ещё не запущен. Запуск происходит при вызове Start:
t1.Start();          // ThreadStart — без аргумента
t2.Start("hello"); // ParameterizedThreadStart — передаём object


Внутри метода поток получает управление не сразу: ОС ставит его в очередь планировщика. Поэтому код после Start() в вызывающем потоке продолжает выполняться параллельно.

Передача данных в поток

ParameterizedThreadStart принимает object — это неудобно, потому что нужно приводить тип внутри метода:
void DoWorkWithParam(object? state)
{
var message = (string)state!;
Console.WriteLine(message);
}


Более чистый способ — захват переменной через лямбду:
string data = "hello from closure";
var t = new Thread(() => Console.WriteLine(data));
t.Start();


Тут важно понимать: лямбда захватывает ссылку на переменную, а не значение. Если data изменится до того, как поток до неё доберётся, поток увидит новое значение.

Фоновые и foreground-потоки

По умолчанию Thread создаётся как foreground (IsBackground = false). Это значит, что процесс не завершится, пока поток не закончит работу. Если нужно обратное поведение:
var t = new Thread(DoWork);
t.IsBackground = true;
t.Start();


Фоновый поток будет убит принудительно при выходе из основного потока.

Подводные камни

Thread.Abort() удалён начиная с .NET 5 — он бросал ThreadAbortException в произвольном месте и приводил к непредсказуемому состоянию программы. Для отмены нужно использовать CancellationToken.

Совместный доступ к данным между потоками требует синхронизации. Без неё возникают гонки, которые воспроизводятся нестабильно и тяжело отлаживаются:
int counter = 0;
var t1 = new Thread(() => { for (int i = 0; i < 10000; i++) counter++; });
var t2 = new Thread(() => { for (int i = 0; i < 10000; i++) counter++; });
t1.Start(); t2.Start();
t1.Join(); t2.Join();
// counter будет меньше 20000 — это гонка
Console.WriteLine(counter);


Чтобы дождаться завершения потока, используют Join. Без него нельзя гарантировать, что поток завершился до следующей строки кода.

Пример кода

Корректный вариант с отменой через CancellationToken:
var cts = new CancellationTokenSource();

var t = new Thread(() =>
{
while (!cts.Token.IsCancellationRequested)
{
Console.WriteLine("Working...");
Thread.Sleep(500);
}
Console.WriteLine("Stopped.");
});

t.IsBackground = true;
t.Start();

Thread.Sleep(2000);
cts.Cancel();
t.Join();


Thread.Start запускает OS-поток, но не гарантирует немедленное выполнение. Данные лучше передавать через замыкания, а не через ParameterizedThreadStart. Для большинства задач сегодня используют Task, async/await или ThreadPool — они не блокируют поток в ожидании и экономят ресурсы.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
👍98❤‍🔥2👏1
🆚 Minimal APIs vs контроллер

ASP.NET Core предлагает два способа строить HTTP API. Minimal APIs и контроллеры. Microsoft в документации рекомендует Minimal APIs для новых проектов, потому что это меньше кода, меньше конфигурации и выше производительность. Но это не значит, что контроллеры устарели. Это значит, что нужен осознанный выбор.

Когда Minimal APIs подходят лучше

Minimal APIs хорошо работают, когда API небольшой и сфокусированный. Микросервисы, внутренние API, легковесные эндпоинты, vertical slice архитектура. Каждый эндпоинт явно определяет маршрут, нет лишней церемонии с наследованием и атрибутами.

Вот как выглядит чистый эндпоинт на Minimal API:
public static class CreateOrderEndpoint
{
public static IEndpointRouteBuilder MapCreateOrder(this IEndpointRouteBuilder app)
{
app.MapPost("/orders", async (
CreateOrderRequest request,
ICreateOrderUseCase useCase,
CancellationToken cancellationToken) =>
{
var result = await useCase.ExecuteAsync(request, cancellationToken);
return Results.Created($"/orders/{result.OrderId}", result);
});

return app;
}
}


Читаемо, прямолинейно, бизнес-логика вынесена в ICreateOrderUseCase.

Когда контроллеры всё ещё уместны

Контроллеры полезны там, где уже есть устоявшаяся MVC-инфраструктура, стандартизированные фильтры, сложное версионирование или большая команда, привыкшая к этому паттерну. Если в проекте выстроены конвенции вокруг контроллеров и это работает, переписывать на Minimal APIs ради моды нет смысла.

Что изменилось в .NET 10

В .NET 10 Microsoft добавила поддержку валидации для Minimal APIs, включая кастомизацию ответов через IProblemDetailsService. Раньше отсутствие встроенной валидации было одним из главных аргументов против Minimal APIs. Теперь этот аргумент слабее.

Главная ловушка обоих подходов

Плохо организованный Minimal API проект превращается в огромный Program.cs. Плохо организованный проект на контроллерах превращается в папку с пустыми pass-through экшенами. Ни один из подходов не спасёт от слабой архитектуры.

Что выбрать

Для новых микросервисов и сфокусированных API автор статьи рекомендует Minimal APIs с vertical slices. Но не сваливать всё в Program.cs, а организовывать эндпоинты по фичам, выносить бизнес-логику из обработчиков маршрутов, использовать явные контракты запросов и ответов.

Для систем с уже выстроенной контроллерной архитектурой или жёсткими корпоративными стандартами контроллеры остаются рабочим выбором.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
6🔥2
💡 C# 16 переосмысляет unsafe: контракты вместо контекста

В C# переделывают модель работы с unsafe. Ключевое слово больше не будет просто «включать указатели». Оно станет контрактом между вызываемым и вызывающим методом. Превью появится в .NET 11, продакшен-релиз в .NET 12.

Проблема

Сейчас unsafe на методе означает «здесь можно использовать указатели». Он не накладывает обязательств на вызывающую сторону. Метод вроде Marshal.ReadByte принимает IntPtr, разыменовывает указатель внутри, но вызывается из безопасного кода без ограничений. Конвенции не проверяет компилятор и не видит ревьюер с первого взгляда.

Что меняется

unsafe в сигнатуре метода теперь означает контракт: вызывающая сторона обязана выполнить задокументированные условия. Каждая небезопасная операция должна быть обёрнута во внутренний блок unsafe { }. Каждый unsafe-метод должен содержать блок /// <safety> с описанием обязательств.

Вот Marshal.ReadByte в новой модели:
/// <safety>
/// Сумма ptr и ofs должна указывать на байт,
/// доступный вызывающей стороне для чтения.
/// </safety>
public static unsafe byte ReadByte(IntPtr ptr, int ofs)
{
byte* addr = (byte*)ptr;
unsafe
{
// SAFETY: полагаемся на обязательство вызывающей стороны.
return addr[ofs];
}
}


Приведение (byte*)ptr безопасно, это преобразование числа. А вот addr[ofs] опасно, потому что разыменовывает указатель. Именно эта строка обёрнута в unsafe { }.

Другие изменения

unsafe на типе теперь вызывает ошибку компиляции. Область действия опускается на уровень методов и свойств. unsafe на методе больше не создаёт unsafe-контекст автоматически. Типы указателей в сигнатуре больше не распространяют unsafe, опасна только операция разыменования. Новое ключевое слово safe обязательно для extern-деклараций, пропустить оба модификатора нельзя.

Включение

Модель включается через новое свойство проекта и работает независимо от <AllowUnsafeBlocks>. Для миграции планируется dotnet format fixer, который механически обернёт вызовы в unsafe { } и перенесёт модификаторы с типов на методы. Писать <safety>-документацию придётся вручную.

Нарушения становятся ошибками компиляции, не предупреждениями. Модель повторяет подход Rust и Swift, но с учётом экосистемы .NET. Для тех, кто использует unsafe, код станет прозрачнее для ревью.

➡️ Блог разработчиков

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
8👍1
🧑‍💻 Генераторы в .NET замедляют билд

Генераторы кода работают внутри компилятора. Они запускаются при каждой сборке и при каждом нажатии клавиши в IDE. Если не следить за ними, билд на 10 секунд может превратиться в полторы минуты без видимых причин. Вот что можно сделать, чтобы этого не допустить.

Сделайте сгенерированный код видимым

По умолчанию сгенерированные .g.cs файлы не записываются на диск. Они существуют только в памяти компилятора. Когда что-то ломается, вы видите ошибку в файле, который не можете открыть.

Добавьте в Directory.Build.props в корне решения:
<Project>
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>
$(BaseIntermediateOutputPath)generated
</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
</Project>

Теперь все сгенерированные файлы попадают в папку obj/generated. Их можно читать, искать по ним и ставить точки останова в отладчике. Это настройка на один раз, и она стоит того.

Замеряйте время через binary log

Ощущение «билд стал медленнее» ничего не даёт. Нужны конкретные числа по каждому генератору. Соберите проект с флагом:
dotnet build -bl

Откройте полученный msbuild.binlog в MSBuild Structured Log Viewer. Найдите задачу CSC. Внутри будет список всех генераторов с временем выполнения в миллисекундах. Собирайте именно полное решение, а не отдельный проект. Стоимость генератора умножается на количество проектов, которые его подтягивают.

Проверьте транзитивные зависимости

Генераторы кода приезжают вместе с NuGet-пакетами. Вы подключаете библиотеку логирования или маппер, а внутри пакета лежит генератор, который теперь запускается в каждом проекте. Вы его не выбирали, но он работает.

В том же binary log задача CSC показывает все загруженные сборки анализаторов и генераторов. Пройдитесь по списку. Если видите незнакомое имя, выясните, откуда оно пришло. Команда dotnet nuget why поможет отследить цепочку зависимостей.

Используйте только инкрементальные генераторы

Если вы пишете свои генераторы, реализуйте IIncrementalGenerator, а не устаревший ISourceGenerator. Инкрементальный генератор кеширует промежуточные результаты. Если входные данные не изменились, он пропускает повторную обработку.

Но сам по себе интерфейс ничего не гарантирует. Генератор, который реализует IIncrementalGenerator и при этом пересобирает всё заново на каждый вызов, работает так же медленно, как устаревший. Кеширование должно быть реальным. Пайплайн нужно строить так, чтобы неизменённые входы давали кешированный выход.

Принимайте решения по числам

Если дорогой генератор пришёл из стороннего пакета, вы не можете его исправить. Но можете решить, стоит ли пакет своего времени сборки. Иногда стоит. Иногда дешевле отказаться от пакета и решить задачу иначе.

Главное тут не гадать, а смотреть в binary log. Там есть имя генератора и его стоимость в миллисекундах. Этого достаточно, чтобы принять осознанное решение.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8❤‍🔥31👾1
📎 NuGet Package Pruning в .NET 10: меньше шума в зависимостях

Если вы запускали NuGet Audit на .NET-проекте, то наверняка видели предупреждения о транзитивных пакетах, которые не устанавливали. Часто это System.Text.Json, System.Memory и подобные, которые уже есть в рантайме в более свежей версии.

Предупреждение есть, а реальной проблемы нет. В .NET 10 эту ситуацию исправили.

В чём была проблема

Многие библиотеки на nuget.org таргетят netstandard2.0 и тянут за собой пакеты вроде System.Text.Json 8.0.0. Проект на .NET 10 уже содержит их в рантайме в более новой версии, но NuGet всё равно резолвит старый пакет в граф. Когда на него публикуется CVE, сканер помечает его как уязвимый.

По факту приложение использует версию из рантайма. Это ложное срабатывание, но отличить его от настоящего без ручного разбора сложно.

Что такое package pruning

Package pruning убирает из графа зависимостей пакеты, которые уже поставляются .NET Runtime Libraries. NuGet при restore сверяется со списком пакетов, входящих в целевой фреймворк. Если транзитивная зависимость попадает в диапазон, она исключается. Пакет не скачивается и не фигурирует в аудите.

Для прямых PackageReference логика другая: NuGet ставит PrivateAssets='all' и IncludeAssets='none'. Ссылка остаётся в csproj, пока вы сами её не удалите. Если пакет можно убрать полностью, NuGet выдаёт NU1510.

Pruning работает только в пределах версии фреймворка. Проект на net8.0 с транзитивной зависимостью System.Text.Json 9.0.0 не уберёт пакет, потому что платформа поставляет только 8.0.x.

Что изменилось в .NET 10

Package pruning впервые появился как opt-in в SDK 9.0.200. В .NET 10 он включён по умолчанию для net10.0 и выше. Одновременно NuGetAuditMode по умолчанию стал all, то есть NuGet проверяет и транзитивные зависимости.

По телеметрии Microsoft, проекты с новыми дефолтами получают на 70% меньше транзитивных предупреждений об уязвимостях. Restore ускоряется до 50% на уровне проекта за счёт меньшего графа.

Дефолтные настройки .NET 10:
<PropertyGroup>
<NuGetAuditMode>all</NuGetAuditMode>
<RestoreEnablePackagePruning>true</RestoreEnablePackagePruning>
</PropertyGroup>


Граф зависимостей теперь точнее отражает то, что приложение реально использует. Меньше ложных срабатываний, быстрее restore, понятнее аудит. Для проектов на .NET 10 всё работает из коробки.

➡️ Блог разработчиков

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11🎉4🔥2👏1
🧠 Microsoft выпустила governance-расширение для MCP-серверов на .NET

Model Context Protocol (MCP) упростил подключение инструментов к AI-агентам. Но чем больше инструментов доступно агенту, тем острее вопрос контроля. Какие tools можно вызывать? Что если в описании инструмента спрятана prompt-инъекция? Как не пропустить опасный ответ обратно в модель?

Microsoft предложила ответ в виде NuGet-пакета Microsoft.AgentGovernance.Extensions.ModelContextProtocol. Это Public Preview расширение для официального MCP C# SDK, которое добавляет слой governance к вашему MCP-серверу одним вызовом.

Что делает пакет

Пакет встраивается в стандартный IMcpServerBuilder и добавляет четыре вещи. Сканирование инструментов при старте, проверку политик при каждом вызове, санитизацию ответов перед возвратом в модель и аудит с метриками.

Установка стандартная:
dotnet add package Microsoft.AgentGovernance.Extensions.ModelContextProtocol


Подключение через один extension-метод:
using AgentGovernance.Extensions.ModelContextProtocol;

builder.Services
.AddMcpServer()
.WithGovernance(options =>
{
options.PolicyPaths.Add("policies/mcp.yaml");
options.DefaultAgentId = "did:mcp:server";
options.ServerName = "contoso-support";
});


Сканирование при запуске

До того как инструменты станут доступны клиентам, пакет проверяет их определения на угрозы. Среди детектируемых категорий: tool poisoning, тайпсквоттинг, скрытые инструкции в описаниях, schema abuse (например, поля вроде password или system_prompt), cross-server атаки. Если инструмент не прошёл проверку, сервер по умолчанию не запустится. Это fail closed на этапе старта, а не рантайм-фильтр.

Политики вызовов

Контроль доступа к инструментам описывается в YAML-файлах. Политики работают по модели allow/deny с приоритетами. Пример:
apiVersion: governance.toolkit/v1
version: "1.0"
name: mcp-governance-policy
default_action: deny
rules:
- name: allow-echo
condition: "tool_name == 'echo'"
action: allow
priority: 10


Если вызов запрещён, пакет вернёт governed error result вместо выполнения инструмента. Политики поддерживают identity: если есть аутентифицированный вызывающий, его идентификатор участвует в оценке. Если нет, используется fallback DID, например did:mcp:anonymous.

Санитизация ответов

Ответы инструментов проходят через санитайзер до возврата клиенту. Он ищет prompt-injection теги (<system>...</system>), фразы переопределения вроде «ignore previous instructions», паттерны утечки credentials и URL для эксфильтрации данных. Опасные фрагменты вырезаются, остальной контент сохраняется.

Дефолты из коробки

Пакет включает защиту по умолчанию без дополнительной настройки. ScanToolsOnStartup, FailOnUnsafeTools, SanitizeResponses, GovernFallbackHandlers, EnableAudit, EnableMetrics — всё это true сразу после подключения.

Итого

Пакет не требует форка SDK и не вводит отдельный прокси-процесс. Он оборачивает финальный ToolCollection в стандартном builder-пайплайне и применяется ко всем инструментам, зарегистрированным до или после вызова .WithGovernance(). Работает с .NET 8+.

Если вы строите MCP-серверы для внутренних копайлотов или корпоративных агентов, это готовый способ добавить контроль доступа, аудит и защиту от инъекций без ручной реализации в каждом сервисе.

➡️ Блог разработчиков

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥21👏1🎉1
⌨️ Как мгновенно отозвать токен в .NET

Обычный JWT нельзя мгновенно отозвать без дополнительной инфраструктуры. Если токен украден, он остаётся валидным до окончания срока действия.

ℹ️ Для сценариев, где доступ нужно отключать сразу, используйте Reference Tokens в Duende IdentityServer:

csharp new Client { ClientId = «banking_app», AccessTokenType = AccessTokenType.Reference };


ℹ️ Теперь токен хранится на сервере и проверяется через интроспекцию. При необходимости его можно отозвать:

csharp await client.RevokeTokenAsync(new TokenRevocationRequest { Address = «https://identity.example.com/connect/revocation», Token = accessToken });


ℹ️ После отзыва API получит:

json { «active»: false }


📌 Когда использовать:

— банковские приложения;
— медицинские системы;
— внутренние сервисы с повышенными требованиями к безопасности;
— критичные операции, где нельзя ждать истечения JWT.

Платой за мгновенный отзыв становится интроспекция токена на стороне сервера

🔗 Читать подробнее

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
👍83😢1🙏1
📢 Навигация по каналу

Чтобы не теряться в потоке постов, собрали удобную навигацию по рубрикам:

#sharp_view — короткие и полезные посты с кодом, best practices, фишки C#, сравнение подходов, code snippets и разбор багов

#il_люминатор — глубокие технические разборы: архитектурные паттерны, производительность, устройство .NET, CLR, GC, JIT и сложные концепции

#async_news — релизы .NET, обновления IDE, анонсы конференций, дайджесты и другие важные новости экосистемы

#entry_point — опросы, холивары, вопросы подписчикам, истории из практики и обсуждения с сообществом

#dotnet_challenge — задачи по C# и .NET, вопросы с собеседований, поиск ошибок в коде и интерактивные викторины

#схема — инфографика, диаграммы, шпаргалки, дорожные карты и визуальное объяснение сложных тем

#garbage_collector — мемы, юмор и забавные истории из жизни .NET-разработчиков

🔈 Используйте рубрики для быстрого поиска интересующих материалов и не пропускайте новые публикации.

🐸 Библиотека шарписта
Please open Telegram to view this post
VIEW IN TELEGRAM
7
💡 Если свойству нужна небольшая логика, больше не обязательно заводить отдельное поле

Раньше приходилось писать так:


private string _email;

public string Email
{
get => _email;
set => _email = value.Trim().ToLowerInvariant();
}


Начиная с C# 14 можно использовать field — ссылку на автоматически сгенерированное backing field:


public string Email
{
get;
set => field = value.Trim().ToLowerInvariant();
}


Подходит для:

валидации значений
нормализации данных
INotifyPropertyChanged
небольшой логики в get/set

⚠️ Если в классе уже есть член с именем field, внутри аксессора приоритет получит новое ключевое слово. Для обращения к своему члену используйте @field или this.field.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
11
This media is not supported in your browser
VIEW IN TELEGRAM
🎓 Где потренировать C# бесплатно

Sololearn предлагает интерактивный курс по C# среднего уровня с практическими заданиями и встроенной AI-проверкой решений.

В программе:

— массивы
— ООП
— структуры и enum
— исключения
— работа с файлами
— generics


Подойдёт тем, кто уже знает основы языка и хочет закрепить их на практике.

🔗 Ссылка

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥6🤔2
💡 StartsWith('s') лучше, чем StartsWith("s")

Если ищете один символ, используйте перегрузку с char:


text.StartsWith(’s’);


вместо:


text.StartsWith(«s»);


То же самое касается:


text.EndsWith('s');
text.IndexOf(’s’);


Перегрузки с char обычно немного эффективнее и сразу показывают, что поиск идёт по одному символу.

📌 Для одного символа — char, для строки или подстроки — string.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
👍84🥱4
🧩 Проверка аргументов на null в современном C#

До .NET 6 проверки обычно выглядели так:


if (arg is null)
throw new ArgumentNullException(nameof(arg));


Сейчас для этого есть встроенный метод:

ArgumentNullException.ThrowIfNull(arg);


Что он даёт:

меньше шаблонного кода
автоматически подставляет имя параметра
единый стиль проверок по всему проекту
проще читать и поддерживать

Например:

 public UserService(IRepository repository)
{
ArgumentNullException.ThrowIfNull(repository);

_repository = repository;
}


Под капотом используется специальная реализация для guard clauses, поэтому этот вариант не только короче, но и хорошо оптимизируется рантаймом.

Используете ThrowIfNull() или по привычке всё ещё пишете if (arg is null)

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
14👍5🥱2
💡 Пагинация в EF Core: Skip/Take не всегда лучший выбор

Самый популярный вариант пагинации в EF Core выглядит так:


var customers = await dbContext.Customers
.AsNoTracking()
.Where(c => c.IsActive)
.OrderBy(c => c.Id)
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToListAsync();


🔵 Под капотом EF Core генерирует SQL с OFFSET/FETCH:


SELECT *
FROM Customers
ORDER BY Id
OFFSET 200 ROWS
FETCH NEXT 100 ROWS ONLY


Для небольших таблиц этого обычно достаточно.

❗️ Но есть нюанс: чем дальше страница, тем больше строк СУБД приходится пропускать перед возвратом результата.
Поэтому для больших таблиц и бесконечной прокрутки часто используют Keyset Pagination:


var customers = await dbContext.Customers
.Where(c => c.Id > lastSeenId)
.OrderBy(c => c.Id)
.Take(pageSize)
.ToListAsync();


Такой подход не требует пропускать тысячи строк и обычно масштабируется заметно лучше.

📌 Простая навигация по страницам → Skip/Take
📌 Большие таблицы и высокая нагрузка → Keyset Pagination

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
👍125🔥5🤔3
pattern matching in csharp.jpg
69.9 KB
❗️ Pattern Matching — одна из тех возможностей C#, которая делает код заметно чище

Вместо проверки типа с последующим приведением можно выполнить обе операции сразу в одном выражении. Меньше шаблонного кода, безопаснее приведение типов и лучше читаемость.

На картинке — простой пример, который стоит взять на вооружение, если ещё пишете код «по-старому» 👀

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10🥱5🔥1
⭐️ Автоматическая регистрация Minimal API через DI

Вместо того чтобы вручную вызывать десятки MapGet() и MapPost(), можно зарегистрировать все endpoint автоматически.

Идея простая:

1️⃣ каждый endpoint реализует общий интерфейс;
2️⃣ приложение находит все реализации через DI;
3️⃣ при запуске вызывает Map() для каждой.

В результате Program.cs сводится буквально к двум строкам:


builder.Services.AddEndpoints(typeof(Program).Assembly);

app.MapEndpoints();


Чем больше проект, тем заметнее становится преимущество такого подхода.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
🥰43💯2🤔1
📍 Task.WhenAll: когда нужно дождаться всех задач

Если несколько операций не зависят друг от друга, не запускайте их по очереди. Запустите сразу все и дождитесь завершения.


var usersTask = LoadUsersAsync(ct);
var ordersTask = LoadOrdersAsync(ct);
var catalogTask = LoadCatalogAsync(ct);

await Task.WhenAll(usersTask, ordersTask, catalogTask);

var users = await usersTask;
var orders = await ordersTask;
var catalog = await catalogTask;


Такой подход позволяет выполнять операции параллельно и уменьшает общее время ожидания.

💡 Несколько советов:

— передавайте CancellationToken во все методы;
— если операций очень много, ограничивайте параллелизм через SemaphoreSlim;
— Task.WhenAll завершится с ошибкой, если хотя бы одна из задач завершилась исключением.

Используйте WhenAll, когда итоговый результат нужен только после завершения всех операций.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3
📌 Списочные паттерны. Разбираем коллекции прямо в switch

Проверка коллекций часто превращается в цепочку условий: сначала Length, потом обращения по индексам, затем ещё несколько if.

В C# 11 появились списочные паттерны (List Patterns), которые позволяют описать форму коллекции прямо в switch.

🅰️ Квадратные скобки задают структуру, .. означает «любое количество элементов», а var позволяет сразу получить нужные значения.


static string Describe(int[] numbers) => numbers switch
{
[] => "пусто",
[var single] => $"один элемент: {single}",
[var first, var second] => $"два: {first} и {second}",
[var first, .., var last] => $"первый {first}, последний {last}",
};


Каждая ветка описывает форму данных, а не последовательность проверок. Не нужно вручную проверять Length и обращаться к элементам по индексам.

🅰️ Особенно удобно разбирать аргументы командной строки или результат Split().


static string Route(string[] args) => args switch
{
["run", var file] => $"запускаем {file}",
["build", .. var rest] => $"сборка с флагами: {string.Join(", ", rest)}",
_ => "неизвестная команда",
};


Обратите внимание на .. var rest: оператор среза не только пропускает элементы, но и позволяет сразу получить их в отдельную коллекцию. Списочные паттерны работают с массивами, списками и другими типами, которые поддерживают индексатор и имеют Length или Count.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥17
📍 Почему TryParse() лучше, чем Parse() внутри try/catch

Невалидный пользовательский ввод — ожидаемый сценарий, а не авария. Такой код работает, но использует исключения для обычной проверки:


try
{
var age = int.Parse(input);
SaveAge(age);
}
catch
{
ShowError();
}


🔜 Намерение понятнее выразить через TryParse():


if (int.TryParse(input, out var age))
{
SaveAge(age);
}
else
{
ShowError();
}


TryParse() сразу показывает: преобразование может не удаться, и этот результат предусмотрен логикой программы. Используйте его для форм, query-параметров, настроек, файлов и других внешних данных.

📌 Ожидаемая ошибка — явная проверка. Исключение — действительно исключительная ситуация.

📍 Навигация: ВакансииЗадачиСобесы

🐸 Библиотека шарписта

#sharp_view
Please open Telegram to view this post
VIEW IN TELEGRAM
🥱61