csharpproglib | Unsorted

Telegram-канал csharpproglib - Библиотека шарписта | C#, F#, .NET, ASP.NET

23284

Все самое полезное для C#-разработчика в одном канале. По рекламе: @proglib_adv Учиться у нас: https://proglib.io/w/b60af5a4 Для обратной связи: @proglibrary_feeedback_bot РКН: https://gosuslugi.ru/snet/67a5c81cdc130259d5b7fead

Subscribe to a channel

Библиотека шарписта | C#, F#, .NET, ASP.NET

😎 Дайджест недели

Я пойду работать в понедельник, ведь в выходные..

Внеплановое обновление .NET 10.0.7

ИИ найдёт слабые места в вашем резюме

Группировка меток в картах MAUI

Sudo for Windows

Native AOT в Node.js

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

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

#async_news

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

🔥 База по ИИ-агентам от научного сотрудника Сколтеха и НИУ ВШЭ

Знакомьтесь, Екатерина Трофимова. Кандидат компьютерных наук, ресерчер в Центре ИИ Сколтеха и лаборатории LAMBDA. Она объединяет глубокую академическую экспертизу и практику: знает, как ИИ-системы устроены «под капотом» и как встроить их в реальные проекты (в т.ч. для Т-банка).

Мы попросили Екатерину собрать список мастхев материалов для тех, кто хочет проектировать агентов в проде. Сохраняйте список.

🛠 Стек и фреймворки:

DSPy — алгоритмическая оптимизация промптов (вместо ручного подбора слов).

Semantic Kernel и LangMem — инструменты для управления сессионной и долгосрочной памятью.

MCP (Model Context Protocol) — новый стандарт от Anthropic для подключения агентов к вашим БД и локальным файлам.

📖 Документация, которую нужно знать:

Anthropic Prompt Caching — как кэшировать контекст и радикально резать косты на API.

OpenAI Agents SDK / Cookbook — лучшие практики работы с памятью.

Augment — платформа для оптимизации работы ИИ-агентов и контроля токенов.

🔬 Хардкорные статьи и препринты (на выходные):

Lost in the Middle — почему LLM «слепнут» на длинных текстах и забывают середину контекста.

How Do Coding Agents Spend Your Money? — куда улетает бюджет при работе автономных кодинг-агентов.

MemGPT — архитектура операционной системы для LLM с иллюзией бесконечной памяти.

InjecAgent / AgentSentry — всё о безопасности и защите агентов от инъекций в промпты.

Екатерина Трофимова — один из ключевых экспертов нашего курса AgentOps. На своих лекциях она детально разбирает, как проектировать инструменты для агентов, как агент принимает решения о вызове инструментов и какие ограничения возникают в реальном проде

🎁 Акция в честь старта продаж!

Прямо сейчас при покупке Инженерного трека вы получаете полный доступ к материалам курса «Разработка ИИ-агентов» в подарок.

👉 Забрать 2 курса по цене 1 и начать обучение

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

🛠 От горизонтальных слоёв к вертикальным срезам

Структура Controllers / Services / Models настолько привычна, что мы перестали её замечать. Но она главный источник боли по мере роста проекта.

Чтобы разобраться в одной фиче, приходится прыгать между тремя папками. Когда фича сложная код размазан по десятку файлов, и никто не держит в голове полную картину

Проблема: классические слои

Фича в типичном проекте выглядит так:

Controllers/ProductsController.cs
Services/IProductService.cs
Services/ProductService.cs
Models/CreateProductRequest.cs


Каждый из файлов знает только о своём слое. Хотите понять одну операцию — обходите весь проект.

Вертикальные срезы

Принцип простой: код, который меняется вместе живёт вместе.

Вместо горизонтальных слоёв на весь проект вертикальные срезы: один срез на одну фичу. Всё нужное в одной папке:
Features/
Products/
CreateProduct/
CreateProduct.cs ← всё здесь


Один файл содержит команду, валидацию и обработчик:
public static class CreateProduct
{
// Входные данные
public record Command(string Name, decimal Price) : IRequest<Guid>;

// Валидация
public class Validator : AbstractValidator<Command>
{
public Validator()
{
RuleFor(x => x.Name).NotEmpty();
RuleFor(x => x.Price).GreaterThan(0);
}
}

// Логика
internal class Handler(AppDbContext db) : IRequestHandler<Command, Guid>
{
public async Task<Guid> Handle(Command req, CancellationToken ct)
{
var product = new Product { Name = req.Name, Price = req.Price };
db.Products.Add(product);
await db.SaveChangesAsync(ct);
return product.Id;
}
}
}


Пример использует MediatR для pipeline и Carter для регистрации. Они удобны, но не обязательны. Главное — положение кода

Что получаем

Высокая связность. Новый разработчик открывает одну папку и видит фичу целиком — от входа до выхода.

Низкое зацепление. Изменения в одном срезе не затрагивают другие. Никакого страха "а вдруг сломается что-то в другом месте".

Конец раздутым сервисам. IProductService неизбежно превращается в класс на тысячу строк с двадцатью ответственностями. Handler делает ровно одно дело.

А что с дублированием

Между срезами иногда появляется похожий код. Это нормально.

Цена небольшого дублирования несравнимо меньше, чем цена высокого зацепления и низкой связности, которые приносят горизонтальные слои. Долгосрочно это огромная победа.

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

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

#sharp_view

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

🛠 Структуры не всегда быстрее

Распространённое заблуждение среди разработчиков на C#, что структуры всегда эффективнее классов. На самом деле это работает только для маленьких структур.

Структуры живут на стеке и не создают нагрузку на GC. Но у этого есть цена: при каждой передаче структуры в метод или присваивании создаётся полная копия. Для маленьких структур это быстро. Для больших не очень.

Вот пример структуры, которая выглядит безобидно, но копирует 64+ байт при каждом вызове:

// Копируется целиком при каждой передаче
public struct BigOrderStruct
{
public int Id;
public string Customer;
public decimal Total;
// ... ещё 12 полей
public List<Item> Items; // это уже ссылочный тип
}


Обратите внимание на List<Items>. Как только структура содержит ссылочные типы, часть преимуществ стека теряется, ведь ссылка всё равно уходит в кучу.

Для маленьких значений без ссылочных типов readonly record struct идеален:
public readonly record struct SmallOrderId(int Id);


Для всего остального readonly record class чаще выигрывает и по читаемости, и по производительности за счёт лучшего поведения кэша.

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

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

#il_люминатор

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

🤩 Валидация для .NET

Валидация входящих данных в .NET-проектах это рутина, которую каждый решает по-своему. Кто-то пишет if (value == null) throw new ArgumentNullException(...) в каждом методе, кто-то тащит FluentValidation и настраивает его под свои нужды. OrionGuard предлагает ещё один вариант: fluent-интерфейс, поддержку ASP.NET Core, MediatR, Blazor, gRPC и SignalR. Всё в одной экосистеме.

Установка базового пакета:

dotnet add package OrionGuard


Простая валидация выглядят так:
using Moongazing.OrionGuard.Core;
using Moongazing.OrionGuard.Extensions;

Ensure.That(email).NotNull().NotEmpty().Email();
Ensure.That(age).InRange(18, 120);


Если нужна производительность без аллокаций, то есть FastGuard на основе Span<T>:
FastGuard.NotNullOrEmpty(name, nameof(name));
FastGuard.Email(email, nameof(email));


Что внутри

Библиотека разбита на 9 пакетов. Каждый подключается отдельно.

OrionGuard — ядро. OrionGuard.AspNetCore — middleware, фильтры, интеграция с IOptions. OrionGuard.MediatR — автоматическая валидация в CQRS-пайплайне. OrionGuard.Generators — source-генераторы для компайл-тайм валидации без рефлексии. OrionGuard.Blazor — интеграция с EditForm. OrionGuard.Grpc и OrionGuard.SignalR — перехватчики для gRPC и SignalR.

Несколько конкретных примеров

Накопление ошибок без исключений:
var result = GuardResult.Combine(
Ensure.Accumulate(email, "Email").NotNull().Email().ToResult(),
Ensure.Accumulate(password, "Password").MinLength(8).ToResult()
);
if (result.IsInvalid)
return BadRequest(result.ToErrorDictionary());


Защита от SQL-инъекций и XSS:
userInput.AgainstSqlInjection(nameof(userInput));
userInput.AgainstXss(nameof(userInput));
filePath.AgainstPathTraversal(nameof(filePath));


Вложенная валидация с путями до поля:
var result = Validate.Nested(order)
.Property(o => o.OrderNumber, p => p.NotEmpty())
.Nested(o => o.Customer, customer => customer
.Property(c => c.Email, p => p.NotEmpty().Email())
.Nested(c => c.Address, address => address
.Property(a => a.ZipCode, p => p.NotEmpty())))
.Collection(o => o.Items, (item, _) => item
.Property(i => i.Quantity, p => p.GreaterThan(0)))
.ToResult();
// Ошибки будут выглядеть так: "Customer.Address.ZipCode", "Items[2].Quantity"


Динамические правила из JSON. Для случаев, когда правила хранятся в базе или конфиге:
var json = """
{
"Rules": [
{ "PropertyName": "Email", "RuleType": "Email" },
{ "PropertyName": "Age", "RuleType": "Range", "Parameters": { "Min": 18, "Max": 120 } }
]
}
""";

var validator = DynamicValidator.FromJson(json);
var result = validator.Validate(userDto);


Source-генератор для NativeAOT:
[GenerateValidator]
public sealed class CreateUserRequest
{
[NotNull, NotEmpty, Length(3, 50)]
public string Name { get; set; }

[NotNull, Email]
public string Email { get; set; }
}

// Валидатор генерируется на этапе компиляции — без рефлексии
var result = CreateUserRequestValidator.Validate(request);


Интеграция с ASP.NET Core:
// Program.cs
builder.Services.AddOrionGuardAspNetCore();

app.MapPost("/api/users", (CreateUserRequest req) => { ... })
.WithValidation<CreateUserRequest>();


Библиотека поддерживает 14 языков для сообщений об ошибках, включая русский. Есть слой совместимости с FluentValidation, миграция сводится к замене using. Все regex-паттерны генерируются через GeneratedRegex, FrozenSet используется для O(1)-поиска в security-паттернах.

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

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

#sharp_view

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

🛠 Native AOT в Node.js: пишем аддоны на C#

Исторически для платформоспецифичных задач в VS Code расширении (например, чтение реестра Windows) использовались нативные аддоны на C++. Их сборка требовала node-gyp и конкретной версии Python на каждой машине разработчика. Для .NET-команды это лишняя зависимость, которую нужно поддерживать и в CI, и при онбординге новых участников.

Решение

Native AOT умеет компилировать .NET-код в нативную shared library с произвольными точками входа. Node.js аддоны требуют только одного: экспортированной функции napi_register_module_v1. Native AOT с этим справляется.

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

Аддон взаимодействует с Node.js через N-API — стабильный C-совместимый интерфейс. Язык реализации значения не имеет, важно лишь соответствие сигнатурам.

Минимальный .csproj:

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<PublishAot>true</PublishAot>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
</Project>


PublishAot говорит SDK собрать нативную библиотеку при публикации. AllowUnsafeBlocks нужен из-за работы с указателями при интеропе с N-API.

Точка входа модуля:
[UnmanagedCallersOnly(
EntryPoint = "napi_register_module_v1",
CallConvs = [typeof(CallConvCdecl)])]
public static nint Init(nint env, nint exports)
{
Initialize();
RegisterFunction(env, exports, "readStringValue"u8, &ReadStringValue);
return exports;
}


Атрибут [UnmanagedCallersOnly] экспортирует метод с нужным именем и calling convention. Суффикс u8 создаёт ReadOnlySpan<byte> с UTF-8 строкой без аллокаций.

Вызов N-API из .NET

N-API функции экспортирует сам node.exe. Чтобы не линковаться с отдельной библиотекой, используется кастомный резолвер через NativeLibrary.SetDllImportResolver:
NativeLibrary.SetDllImportResolver(
Assembly.GetExecutingAssembly(),
(libraryName, assembly, searchPath) =>
libraryName is "node"
? NativeLibrary.GetMainProgramHandle()
: 0);


После этого P/Invoke с именем "node" будет резолвиться в хост-процесс:
[LibraryImport("node", EntryPoint = "napi_create_string_utf8")]
internal static partial Status CreateStringUtf8(
nint env, ReadOnlySpan<byte> str, nuint length, out nint result);


Пример экспортируемой функции

Функция читает значение из реестра Windows и возвращает его в JavaScript:
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static nint ReadStringValue(nint env, nint info)
{
try
{
var keyPath = GetStringArg(env, info, 0);
var valueName = GetStringArg(env, info, 1);

if (keyPath is null || valueName is null)
{
ThrowError(env, "Expected two string arguments: keyPath, valueName");
return 0;
}

using var key = Registry.CurrentUser.OpenSubKey(keyPath, writable: false);

return key?.GetValue(valueName) is string value
? CreateString(env, value)
: GetUndefined(env);
}
catch (Exception ex)
{
ThrowError(env, $"Registry read failed: {ex.Message}");
return 0;
}
}


Исключения нужно обязательно перехватывать: необработанное исключение в методе с [UnmanagedCallersOnly] крашит хост-процесс. ThrowError пробрасывает ошибку в JavaScript как стандартный Error.

Что в итоге

Команда убрала зависимость от Python и node-gyp. Теперь yarn install работает только с Node.js и .NET SDK, которые и так нужны для разработки. CI стал проще, онбординг быстрее.

Производительность сопоставима с C++-реализацией: Native AOT генерирует оптимизированный нативный код, а для задач типа доступа к реестру и маршалинга строк разницы на практике нет.

➡️ Источник

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

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

#sharp_view

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

❗️ Внеплановое обновление .NET 10.0.7

Microsoft выпустила внеплановый патч для .NET 10. Причина — баг в пакете Microsoft.AspNetCore.DataProtection, который попал в релиз вместе с плановым обновлением 10.0.6.

Что случилось

После выхода 10.0.6 пользователи начали сообщать, что расшифровка данных перестала работать. Microsoft изучила проблему и обнаружила, что регрессия также открывает уязвимость CVE-2026-40372.

Суть бага: управляемый энкриптор вычислял HMAC-тег не над теми байтами полезной нагрузки, а потом и вовсе выбрасывал результат. Это приводило к повышению привилегий. Затронуты версии Microsoft.AspNetCore.DataProtection от 10.0.0 до 10.0.6 включительно.

Кого это касается

Всех, кто использует ASP.NET Core Data Protection в приложениях на .NET 10. Это стандартный механизм защиты данных в ASP.NET Core — сессии, антифоргери-токены, куки и всё, что шифруется через IDataProtector.

Что делать

Обновить пакет Microsoft.AspNetCore.DataProtection до версии 10.0.7 как можно скорее:

dotnet add package Microsoft.AspNetCore.DataProtection --version 10.0.7


После обновления пакета пересобрать и перевыложить приложение. Проверить текущую версию SDK можно командой:
dotnet --info


➡️ Источник

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

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

#async_news

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

🧑‍💻 ИИ найдёт слабые места в вашем резюме

Лайфхак, который используют единицы: найти профили людей с нужным тайтлом, которые уже устроились и проанализировать их через ИИ.

200–300 профилей дадут чёткую картину: в чём вы сильнее рынка, где сливаете на фоне конкурентов и что исправить в резюме прямо сейчас.

➡️ Это лишь один из 5 промптов из статьи

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

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

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

🗺 Группировка меток в картах MAUI

Если на карте десятки или сотни меток, они сливаются в непроходимую кашу. В .NET MAUI 11 Preview 3 появилась кластеризация меток из коробки на Android и iOS/Mac Catalyst.

Как включить

Одно свойство:

<maps:Map IsClusteringEnabled="True" />


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

Разные группы кластеров

Если нужно, чтобы кофейни кластеризовались отдельно от парков, используем ClusteringIdentifier на Pin:
map.Pins.Add(new Pin
{
Label = "Pike Place Coffee",
Location = new Location(47.6097, -122.3331),
ClusteringIdentifier = "coffee"
});

map.Pins.Add(new Pin
{
Label = "Occidental Square",
Location = new Location(47.6064, -122.3325),
ClusteringIdentifier = "parks"
});


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

Обработка нажатия на кластер

При тапе на кластер срабатывает событие ClusterClicked:
map.ClusterClicked += async (sender, e) =>
{
string names = string.Join("\n", e.Pins.Select(p => p.Label));
await DisplayAlert(
$"Кластер ({e.Pins.Count} меток)",
names,
"OK");

// Отменить стандартное приближение к кластеру:
// e.Handled = true;
};


В ClusterClickedEventArgs три поля:

Pins — список пинов в кластере
Location — географический центр
Handled — если true, стандартное поведение (зум к кластеру) отменяется.

Платформенные детали

На Android кластеризация работает через собственный алгоритм на основе сетки, пересчитывается при смене масштаба. Сторонние зависимости не нужны.

На iOS и Mac Catalyst используется нативный MKClusterAnnotation из MapKit — анимации и поведение платформенные.

Пример с кластеризацией уже есть в репозитории, страница Clustering в проекте MapDemo.

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

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

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

#il_люминатор

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

🧑‍💻 Больше не нужно открывать терминал от администратора

На Linux и macOS всё просто: нужны права администратора, добавили sudo перед командой. На Windows для этого приходилось открывать новый терминал от имени администратора, делать правый клик, подтверждать UAC. Microsoft наконец добавила sudo в Windows 11 нативно, начиная со сборки 26045.

Что это такое

Sudo for Windows позволяет запускать команды с правами администратора прямо из обычного терминала, без переоткрытия сессии. Это не порт Unix-утилиты, а отдельная Windows-реализация той же идеи. Некоторые детали поведения отличаются от привычного sudo на Linux.

Как включить

Через настройки: Параметры → Система → Для разработчиков → Включить sudo.

Или через командную строку с правами администратора:

sudo config --enable normal


Три режима работы

forceNewWindow — режим по умолчанию. Команда запускается в новом окне с правами администратора. Похоже на runas, но удобнее.

disableInput — команда выполняется в текущем окне, но без возможности ввода. Подходит для автоматических задач, где интерактивный ввод не нужен.

normal — полный инлайн-режим, как на Linux. Команда запускается в текущем окне и может принимать ввод. Наиболее удобный вариант, но несёт больше рисков безопасности.

Переключение между режимами:
sudo config --enable forceNewWindow
sudo config --enable disableInput
sudo config --enable normal


Примеры использования

Редактирование системного файла:
sudo notepad C:\Windows\System32\drivers\etc\hosts


Установка глобального npm-пакета:
sudo npm install -g package-name


Проверка сетевых соединений:
sudo netstat -ab


Про безопасность

При запуске через sudo появляется UAC-запрос на подтверждение. Режимы disableInput и normal несут дополнительные риски: нежелательный процесс с низкими правами теоретически может взаимодействовать с повышенным процессом. Для большинства сценариев рекомендован forceNewWindow.

Что нужно учесть

Инструмент доступен только на Windows 11, начиная с версии 24H2. Управлять им можно через настройки, командную строку или реестр (HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Sudo). Есть и GPO-политика для корпоративного управления.

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

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

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

#sharp_view

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

✏️ Паттерн репозиторий: неочевидные моменты

Репозиторий скрывает логику работы с данными за интерфейсом. Контроллер или сервис не знает, откуда берётся объект: из базы, кеша или файла. Это удобно, но у паттерна есть нюансы, о которых редко говорят.

Зачем он вообще нужен

Без репозитория бизнес-логика тесно связана с DbContext. Тесты требуют реальной базы или громоздких моков. При смене ORM надо переписывать половину приложения.

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

Неочевидный момент 1: SaveChanges внутри репозитория

Если вызывать SaveChangesAsync() в каждом методе репозитория, теряется возможность атомарно сохранить несколько операций. Например, создать пользователя и сразу выдать ему роль в одной транзакции не получится.

Решение: убрать SaveChanges из репозитория и перенести его в Unit of Work или вызывать явно на уровне сервиса.

public interface IUnitOfWork : IDisposable
{
IUserRepository Users { get; }
IRoleRepository Roles { get; }
Task<int> SaveChangesAsync();
}

public class UnitOfWork : IUnitOfWork
{
private readonly AppDbContext _context;

public IUserRepository Users { get; }
public IRoleRepository Roles { get; }

public UnitOfWork(AppDbContext context)
{
_context = context;
Users = new UserRepository(context);
Roles = new RoleRepository(context);
}

public async Task<int> SaveChangesAsync()
=> await _context.SaveChangesAsync();

public void Dispose() => _context.Dispose();
}


Неочевидный момент 2: Generic-репозиторий не всегда помогает

Соблазн велик написать один GenericRepository<T> и использовать везде:
public class GenericRepository<T> : IRepository<T> where T : class
{
protected readonly AppDbContext _context;
protected readonly DbSet<T> _dbSet;

public GenericRepository(AppDbContext context)
{
_context = context;
_dbSet = context.Set<T>();
}

public async Task<T?> GetByIdAsync(int id) => await _dbSet.FindAsync(id);
public async Task<IEnumerable<T>> GetAllAsync() => await _dbSet.ToListAsync();
public async Task AddAsync(T entity) => await _dbSet.AddAsync(entity);
public void Update(T entity) => _dbSet.Update(entity);
public void Delete(T entity) => _dbSet.Remove(entity);
}


Проблема в том, что специфичные запросы всё равно придётся добавлять. Найти пользователя по почте через универсальный метод не выйдет без дополнительных расширений. В итоге generic-репозиторий обрастает методами и теряет смысл.

Лучше наследовать от него там, где действительно нет специфики, а сложные сущности описывать отдельными интерфейсами:
public interface IUserRepository : IRepository<User>
{
Task<User?> GetByEmailAsync(string email);
Task<IEnumerable<User>> GetActiveUsersAsync();
}

public class UserRepository : GenericRepository<User>, IUserRepository
{
public UserRepository(AppDbContext context) : base(context) { }

public async Task<User?> GetByEmailAsync(string email)
=> await _dbSet.FirstOrDefaultAsync(u => u.Email == email);

public async Task<IEnumerable<User>> GetActiveUsersAsync()
=> await _dbSet.Where(u => u.IsActive).ToListAsync();
}


Неочевидный момент 3: EF Core уже репозиторий

DbContext и DbSet<T> это и есть реализация паттерна репозиторий. DbSet — коллекция объектов, DbContext — Unit of Work. Оборачивать EF Core в ещё один слой репозитория иногда только добавляет лишний код.

Это оправдано, когда нужно:
- тестировать бизнес-логику без базы (мок интерфейса проще, чем мок DbContext)
- поддерживать несколько источников данных
- изолировать слои приложения архитектурно

Если проект небольшой и планов менять ORM нет, прямое использование DbContext в сервисах может быть вполне нормальным решением.

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

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

#il_люминатор

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

🏃‍♀️ Уже завтра стартует курс по разработке AI-агентов.

Про AI-агентов часто думают, что это просто модная обертка над джпт для пет-проектов. Кажется, прикрутил API к скрипту и типа готово. А вот и нет! Когда дело доходит до прода, начинаются настоящие проблемы.

Зачем глубоко копать мультиагентные системы, если можно обойтись старым добрым кодом? Как контролировать расходы на токены, чтобы новая фича не разорила бизнес? Как заставить агента работать стабильно и предсказуемо, а не галлюцинировать?


Эту инженерную часть мы и будем разбирать на курсе. Будем учиться интегрировать внешние API, работать с RAG, LangGraph, CrewAI и деплоить всё это так, чтобы работало как часы.

Стартуем завтра. Для участия и доступа к программе переходите по ссылке.

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

🔍 ILSpy 10.0: поддержка C# 14, API diff и переход на .NET 10

Вышел крупный релиз ILSpy. Релиз собран на .NET 10, так что перед обновлением убедитесь, что он у вас установлен.

Новые языковые возможности

Декомпилятор теперь понимает конструкции C# 13 и C# 14. Добавлена поддержка ограничения allows ref struct из C# 13 и extension-членов из C# 14. Также подтянули поддержку InlineArray из C# 12 — на удивление поздно, но лучше поздно чем никогда. Детекция паттернов обновлена до Roslyn 5.0 RTM.

Новое в интерфейсе

Появился выделенный UI для работы с C# 14 extension-членами. Добавлена навигация по видимой истории — теперь можно перемещаться по посещённым узлам в дереве сборок. Из поставки убрали переводы интерфейса — проект сосредоточился на английском.

API diff

Одна из самых интересных новинок. ILSpy теперь умеет сравнивать публичное API двух сборок и показывать разницу. Полезно при анализе изменений между версиями библиотек, когда исходников нет.

Улучшения декомпилятора

Исправлена корректная декомпиляция pre-increment операторов, unmanaged function pointers и локальных функций с параметрами по умолчанию. Улучшено определение record-типов и primary constructors. CSharpConversions получил общие улучшения для правил C# вплоть до версии 9. Дизассемблер теперь поддерживает формат ildasm /caverbal.

Пакетная генерация PDB

Новый метод GeneratePdbForAssemblies заменил старый GeneratePdbForAssembly — теперь можно генерировать PDB для нескольких сборок за один вызов.

Чистка API

Удалены устаревшие типы: UnresolvedUsingScope, ToTypeReference, ITypeReference. ResolvedUsingScope переименован в UsingScope. Если вы используете ICSharpCode.Decompiler как NuGet-пакет в своих инструментах — стоит проверить совместимость.

Производительность

Убрана XML-сериализация из DecompilerSettings — настройки теперь сохраняются быстрее.

Важное предупреждение

Команда отдельно предупреждает: домен ilspy.org им не принадлежит. Скачивать ILSpy нужно только с GitHub Releases.

➡️ Источник

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

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

#async_news

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

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

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

#garbage_collector

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

👀 Смотрим внутрь любой .NET-сборки

Бывает, что нужно понять, что делает библиотека без исходников, разобраться в чужом коде или проверить, что именно скомпилировал компилятор.

ILSpy решает эту задачу это открытый декомпилятор .NET-сборок, который превращает IL-байткод обратно в читаемый C#.

ILSpy открывает любой .dll или .exe и показывает декомпилированный C#-код с навигацией по типам, методам и свойствам. Поддерживает поиск по всей сборке, переходы по гиперссылкам между типами и историю навигации.

Visual Studio 2022 и 2026 используют движок ILSpy внутри для F12-навигации по декомпилированным источникам, так что если вы нажимали «Go to Definition» на тип из NuGet-пакета, вы уже работали с ILSpy.

CLI-инструмент для Linux, Mac и Windows:

dotnet tool install -g ilspycmd


После установки можно декомпилировать прямо из терминала:
ilspycmd MyLibrary.dll


Использование как библиотеки

Движок декомпилятора доступен отдельным NuGet-пакетом ICSharpCode.Decompiler. Его можно встроить в собственные инструменты:
var decompiler = new CSharpDecompiler("MyLibrary.dll", new DecompilerSettings());
var code = decompiler.DecompileWholeModuleAsString();
Console.WriteLine(code);


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

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

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

#sharp_view

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

📰 Любимый автор по C#

Кого смотрите и читаете по C# и .NET? Авторы на YouTube, Хабре, телеграм-каналы, подкасты — пишите в комменты 💬

Мы, естественно, следим за Ником Чапсасом, но может есть менее гигантские медиа-личности?

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

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

#entry_point

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

🔄 Мелкий релиз с нужными правками API

Вышел релиз v6.2.0 библиотеки OrionGuard для валидации в .NET. Версия небольшая, но устраняет несколько неудобств, которые вылезли при написании демо к v6.1.0.

Что добавили

Появился интерфейс IStronglyTypedId<TValue> — теперь strongly-typed ID на основе record и ID, сгенерированные source-генератором через struct, работают под одним контрактом. Guards и DI-хелперы принимают оба варианта без дополнительного кода.

Добавили абстрактный record DomainEventBase. Раньше в каждом доменном событии нужно было вручную объявлять EventId и OccurredOnUtc. Теперь достаточно унаследоваться:

public sealed record OrderShippedEvent(OrderId OrderId) : DomainEventBase;


Сгенерированные struct-идентификаторы теперь реализуют IParsable<TSelf> и ISpanParsable<TSelf>. Это значит, что route-биндинг в ASP.NET Core minimal API работает из коробки без кастомных конверторов.

Source-генератор стал умнее: он проверяет во время компиляции, подключён ли EF Core, и генерирует ValueConverter<,> только если да. Лишний код в проект не попадает.

Что изменили

Все sub-пакеты переименованы — префикс Moongazing. убрали из NuGet PackageId:

Moongazing.OrionGuard.AspNetCoreOrionGuard.AspNetCore
Moongazing.OrionGuard.MediatROrionGuard.MediatR
Moongazing.OrionGuard.GeneratorsOrionGuard.Generators

И так далее для Swagger, OpenTelemetry, Blazor, Grpc, SignalR.

C#-пространства имён и структура папок не изменились. Существующие using-директивы продолжают работать.

Миграция с v6.1.0

В .csproj нужно обновить ссылки на пакеты: заменить Moongazing.OrionGuard.X на OrionGuard.X. Код трогать не нужно. Если хочется убрать бойлерплейт в доменных событиях — можно добавить : DomainEventBase, но это опционально.

➡️ Источник

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

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

#async_news

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

🤩 .NET 10 в Ubuntu 26.04

Вышел Ubuntu 26.04 LTS. Вместе с ним официальная поддержка .NET 10 прямо из стандартного репозитория.

Две команды и SDK готов:

sudo apt update
sudo apt install dotnet-sdk-10.0


Проверить, что всё работает:
dotnet run - << 'EOF'
using System.Runtime.InteropServices;

Console.WriteLine($"Hello {RuntimeInformation.OSDescription} from .NET {RuntimeInformation.FrameworkDescription}");
EOF


Вывод будет примерно таким:
Hello Ubuntu Resolute Raccoon from .NET .NET 10.0.5


Это так называемый file-based app. Он передаётся через stdin напрямую в dotnet run. Стандартный unix-подход.

Контейнеры

Образы с тегом resolute уже доступны. Если вы использовали -noble, достаточно поменять суффикс:
sed -i "s/noble/resolute/g" Dockerfile.chiseled


После этого собрать и запустить с ограничениями ресурсов:
docker build --pull -t aspnetapp -f Dockerfile.chiseled .
docker run --rm -it -p 8000:8080 -m 50mb --cpus .5 aspnetapp


Chiseled-образы остались, ничего не убрали

Native AOT

Если нужен маленький бинарь с быстрым стартом — dotnet-sdk-aot-10.0 теперь в репозитории Ubuntu.

Установка:
apt install -y dotnet-sdk-aot-10.0 clang


Публикация простого приложения:
dotnet publish app.cs


Результат:
1.4M    artifacts/app/app
3.0M artifacts/app/app.dbg


Запуск занял 3 миллисекунды. Для веб-сервиса с PublishAot=true итоговый размер около 13 МБ вместе с метаданными System.Text.Json.

.NET 8 и 9 не входят в основной репозиторий Ubuntu 26.04, но доступны через отдельный PPA от Canonical:
apt install -y software-properties-common
add-apt-repository ppa:dotnet/backports


После подключения появятся пакеты dotnet-sdk-8.0, dotnet-sdk-9.0 и соответствующие aspnetcore-runtime-*. Поддержка там на уровне "best-effort", то есть официально, но без гарантий уровня LTS.

Что важно

Ubuntu 26.04 принёс три заметных изменения на уровне ОС:

• Linux 7.0 — команда .NET начнёт тестирование, как только получит VM.

• Постквантовая криптография — поддержка уже есть в .NET 10.

• Удаление cgroup v1. Переход на cgroup v2 в .NET сделали несколько лет назад, так что сломаться ничего не должно.

Если переходите на Ubuntu 26.04 в продакшне, то всё основное готово с первого дня.

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

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

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

#async_news

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

👋 Небольшой вопрос для прокачки

Это классика технических интервью в C#:

Статический конструктор — когда именно его вызывает рантайм


Вы не пишете new MyClass(), не обращаетесь к объекту, не делаете вообще ничего явного. А он всё равно срабатывает.

➡️ Узнайте ответ

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

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

#dotnet_challenge

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

⚙️ Сервер тормозит без видимых причин

Представьте картину: CPU загружен на 30–40%, ошибок нет, но запросы внезапно начинают тормозить, а время ответа под нагрузкой улетает в небо. Это не магия и не баг в инфраструктуре. Скорее всего, вы столкнулись с голоданием пула потоков.

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

Причина почти всегда в одном из таких паттернов:

var data = httpClient.GetStringAsync(url).Result; // блокирует поток

Task.Run(() => DoWork()).Wait(); // форсированная синхронизация


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

Попробуйте сделать так и замеряйте результат:
var data = await httpClient.GetStringAsync(url); 


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

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

#il_люминатор

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

🧑‍💻 Подборка вакансий для шарпистов

Старший Backend C# ASP. NET разработчик — от 300 000 до 400 000 ₽ , удалёнка или гибрид в Москве

Разработчик C# — офис в Минске

Unity Developer — удалёнка

➡️ Еще больше топовых вакансий — в нашем канале C# Jobs

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

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

🗓 В следующий вторник (28.04) в 19:00 встречаемся в онлайне.

Тема:

Как эффективно управлять контекстным окном LLM в мультиагентных системах и не сливать бюджет на токены


В кружке Кирилл рассказал, какие именно подходы будем разбирать.

👉 Занять место на вебинаре

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

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

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

#garbage_collector

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

💡 Фильтрация коллекции по типу

Когда в коллекции хранятся объекты разных типов, обычный перебор с ручной проверкой is или as быстро превращается в шаблонный код. OfType<T> решает это в одну строку.

Пример:

var mixedList = new List<object> { 1, "hello", 2.5, "world", 3, true };

var stringsOnly = mixedList.OfType<string>();

// Результат: hello, world


Из шести элементов метод выбирает только строки. Числа, булево значение и double игнорируются.

Как это работает под капотом

OfType<T> использует оператор is для каждого элемента. Примерно так выглядит его реализация:
public static IEnumerable<T> OfType<T>(this IEnumerable source)
{
foreach (var item in source)
{
if (item is T typed)
yield return typed;
}
}


Метод ленивый: перебор происходит только при итерации по результату, не сразу при вызове.

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

Удобно при работе с IEnumerable<object>, коллекциями ArrayList из старого кода, или когда нужно вытащить конкретный тип из гетерогенной иерархии объектов.

var textBoxes = Controls.OfType<TextBox>();


Чем отличается от Cast<T>

Cast<T> бросает InvalidCastException, если встречает несовместимый тип. OfType<T> просто пропускает такой элемент. Если коллекция точно однородная и ошибка должна сигнализировать о проблеме, уместен Cast<T>. В остальных случаях безопаснее OfType<T>.

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

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

#sharp_view

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

⚡️ Мы рады представить команду экспертов курса AgentOps!

Дмитрий Антипов расскажет, как грамотно проверить работу AI-моделей
Курилл Кухарев поделится, почему компаниям выгодно использовать локальные модели и как их развернуть
Андрей Носов расскажет, как работать с данными и знаниями в AI-системах: построение RAG, выбор подходов к поиску и организация хранения данных
Антон Будняк разберет, как обеспечить устойчивость сервиса, в котором используется ИИ
Александр Ошурков расскажет, как оценивать качество работы LLM в backend-сервисах
Екатерина Трофимов разберет, как проектировать инструменты для AI-агентов и выстраивать взаимодействие с внешними сервисами

Курс для backend-разработчиков, тимлидов и LLM инженеров о том, как внедрять AI-логику в бэкенд IT-продуктов и сохранять стабильность сервиса.

К концу обучения вы получите:

• Структурированный подход к архитектуре и деплою AI-агентов
• Навыки настройки мониторинга, тестирования и контроля расходов на токены
• Разбор сложных инженерных кейсов из реальной практики

🎁 Доступ к материалам курса
«Разработка ИИ-агентов» в подарок при покупке Инженерного трека

👉 Все подробности и программа обучения.

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

🤩 Фоновые задачи без лишних зависимостей

Если вы запускаете фоновые задачи через Task.Run или городите велосипед поверх IHostedService, знакомьтесь с BusyBee. Это небольшая библиотека для in-memory обработки фоновых задач в .NET, которая построена на нативных каналах (System.Threading.Channels).

Какую проблему решает

Хочется просто поставить задачу в очередь и не думать об управлении потоками, таймаутах и наблюдаемости. Стандартные средства .NET это умеют, но требуют немало бойлерплейта. Hangfire или Quartz — тяжеловато, если нужно только in-memory без персистентности.

Что умеет BusyBee

Ограниченные и неограниченные очереди с настройкой поведения при переполнении: можно выбросить исключение, подождать, отбросить самую старую или новую задачу. Параллельное выполнение задач с настраиваемым пулом слотов. Глобальные и per-job таймауты. Интеграция с OpenTelemetry для трассировки и метрик. Полная поддержка DI и CancellationToken.

Как подключить:

dotnet add package BusyBee


Регистрация в DI:
builder.Services
.AddBusyBee()
.WithUnboundedQueue()
.WithGlobalJobTimeout(TimeSpan.FromSeconds(30))
.WithLevelOfParallelism(10);


Поставить задачу в очередь:
await queue.Enqueue(async (services, context, cancellationToken) =>
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogInformation("Обрабатываем задачу {JobId}", context.JobId);
await Task.Delay(1000, cancellationToken);
}, cancellationToken);


Каждая задача получает контекст с JobId, временем постановки в очередь и временем старта. Через services доступны любые зарегистрированные сервисы.

Ограниченная очередь:
builder.Services
.AddBusyBee()
.WithBoundedQueue(capacity: 1000, OverflowStrategy.DropOldest);


Подключение OpenTelemetry:
builder.Services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.AddSource(BusyBee.Observability.TracingConstants.TraceSourceName)
.AddConsoleExporter())
.WithMetrics(metrics => metrics
.AddMeter(BusyBee.Observability.MetricsConstants.MeterName)
.AddPrometheusExporter());


Обработка ошибок

Можно подключить собственные обработчики для сбоев, таймаутов и ручной отмены задач через реализацию интерфейсов IJobFailureHandler, IJobTimeoutHandler и IManualCancellationHandler:
services.AddBusyBee()
.WithJobFailureHandler<MyCustomJobFailureHandler>();


BusyBee подойдёт, когда нужен простой in-memory обработчик в фоне без внешних зависимостей и баз данных.

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

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

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

#sharp_view

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

📰 Новостной дайджест

Свежая газетка у вас на пороге

Хирургия над контекстным меню Windows 11

Фриланс для разработчиков

Не фриланс для разработчиков

.NET 11 Preview 3

Обновления безопасности 2026 .NET

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

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

#async_news

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

🦾 Надоело чинить «упавших» ИИ-агентов после каждого микросбоя внешних сервисов?

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

🗓 Ждем вас 28 апреля в 19:00 МСК на эфире: «Как эффективно управлять контекстным окном LLM в мультиагентных системах и не сливать бюджет на токены».

👉 Кто вещает и в чем польза?

Спикер Кирилл Кухарев (Senior AI Engineer в Raft, спикер AI Conf и Highload++). Он реализовал более 50 коммерческих проектов в GenAI и на вебинаре покажет, как взять под контроль работу нескольких агентов, чтобы они не перекидывали друг другу лишний контекст и не сжигали ваши деньги.

В прямом эфире разберем:
• Как формируется контекст в LLM при маршрутизации между агентами;
• Куда утекают лишние токены и возникает перерасход;
• Практические методы: как сжимать историю, грамотно делить задачи, лимитировать передачу контекста и собирать промпты прямо в процессе запроса пользователя.

🔥 Два способа получить максимум:

1. Приходите на вебинар 28 апреля. Дарим участникам промокод на 5.000 ₽ (работает 3 дня после эфира - это шанс забрать курс по самому низу рынка).

2. Выбирайте Инженерный трек. В подарок к нему идет полный доступ к записям и автопроверкам завершенного курса «Разработка ИИ-агентов».

👉 Занять место на вебинаре и стать профи в AgentOps

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

👨‍💻 Не показывайте технические ошибки пользователям

Когда приложение падает с необработанным исключением, стандартное поведение многих фреймворков — вывести стек вызовов прямо в ответ. Пользователь видит внутренние пути файлов, названия классов, фрагменты кода. Для злоумышленника это готовая карта уязвимостей.

Что происходит на практике

Допустим, в ASP.NET приложении возникает необработанное исключение. Без явной настройки middleware фреймворк вернёт подробный ExceptionDetails с именем метода, строкой кода и трассировкой стека. Атакующий получает информацию о структуре проекта, версиях библиотек и логике работы приложения без каких-либо усилий.

Как это закрыть

Правильный подход это перехват всех необработанныъ исключений. Нужно логировать их внутри системы и возвращать пользователю только нейтральное сообщение.

В ASP.NET это делается через UseExceptionHandler:

app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
var exceptionHandlerPathFeature =
context.Features.Get<IExceptionHandlerPathFeature>();

var logger = context.RequestServices
.GetRequiredService<ILogger<Program>>();

logger.LogError(exceptionHandlerPathFeature?.Error,
"Unhandled exception");

context.Response.StatusCode = 500;

await context.Response.WriteAsJsonAsync(new
{
message = "Something went wrong. Please try again later."
});
});
});


Что здесь происходит: middleware перехватывает исключение, передаёт его в ILogger — туда, где его увидит только команда разработки — и возвращает клиенту простой JSON с универсальным сообщением об ошибке. Никаких деталей реализации наружу не уходит.

ILogger пишет в вашу систему мониторинга — будь то Application Insights, Seq, Serilog или любой другой инструмент. Вы по-прежнему видите полный стек вызовов и можете разобраться в причине ошибки. Пользователь при этом получает понятное сообщение, а не технический мусор.

Это базовая практика защиты приложений. Она не требует сложной архитектуры, достаточно одного middleware, настроенного в точке входа приложения.

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

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

#sharp_view

Читать полностью…

Библиотека шарписта | C#, F#, .NET, ASP.NET

Что выведет код с картинки

💡 Подсказка: обратите внимание на конструкцию when. Это не просто синтаксический сахар.

➡️ А ответ тут

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

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

#dotnet_challenge

Читать полностью…
Subscribe to a channel