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

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

Кто куда, а админ на шашлыки.

hh.ru + Госуслуги + трудовая

VSTest убирает зависимость от Newtonsoft.Json

Как настроить JsonSerializerOptions

SkiaSharp 4.0

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

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

#async_news

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

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

📉 Кандидатов мало, а собесы становятся сложнее

Казалось бы, в кризис найма логично снижать барьеры. Но Яндекс и Сбер делают ровно наоборот: добавляют этапы, запускают ИИ-скрининг, вводят психологические тесты и проверку культурного фита.

Поиск работы даже для сильных специалистов теперь занимает 6–9 месяцев, а воронка найма растёт.

➡️ Разбираем на цифрах и фактах

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

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

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

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

✏️ Пятничный вопрос с собеса

Чем SelectMany отличается от Select в LINQ


Оба метода принимают функцию-проекцию. Оба работают с коллекциями. Но результат — принципиально разный.

var data = new List<List<int>>
{
new() { 1, 2, 3 },
new() { 4, 5, 6 }
};

var a = data.Select(x => x); // ???
var b = data.SelectMany(x => x); // ???


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

Ответ: спрятали не только под спойлер

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

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

📎 JsonSerializerOptions: дефолты vs продакшен в .NET 10

System.Text.Json работает из коробки, но умолчания выбирались для удобства, а не безопасности. Разбираем пять настроек, которые стоит менять перед релизом.

Дублирующиеся ключи

{"role":"user","role":"admin"}


По умолчанию десериализатор молча возьмёт admin. Именно это эксплуатировал CVE-2022-25757: шлюз видел одно значение, бэкенд — другое. Фикс: AllowDuplicateProperties = false.

Неизвестные поля

Поля, которых нет в модели, тихо выбрасываются. Переименовали свойство в DTO и забыли обновить клиент — данные исчезают без ошибки. Фикс: UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow.

required компилятора ≠ required для десериализатора

C# 11 следит, чтобы объект нельзя было создать без обязательных полей. Десериализатор работает через рефлексию и обходит этот контроль — запишет null туда, где поля нет в JSON. Баг всплывёт на три слоя глубже в виде NullReferenceException. Фикс: RespectRequiredConstructorParameters = true.

Регистр не важен — по умолчанию

ASP.NET Core включает PropertyNameCaseInsensitive = true в веб-дефолтах. IsAdmin, isadmin и ISADMIN — одно и то же. Если рядом есть middleware с проверкой строк по сырому JSON — они уже видят разные вещи. Фикс: PropertyNameCaseInsensitive = false.

Nullable-аннотации игнорируются

string Name говорит, что поле не может быть null. Десериализатор это не соблюдает — запишет null без предупреждений. Фикс: RespectNullableAnnotations = true.

В .NET 10 всё это одна строка:
var options = JsonSerializerOptions.Strict;


Для ASP.NET Core пайплайна JsonOptions.SerializerOptions не заменяется целиком, флаги выставляются вручную:
builder.Services.Configure<JsonOptions>(o =>
{
var s = o.SerializerOptions;
s.AllowDuplicateProperties = false;
s.UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow;
s.PropertyNameCaseInsensitive = false;
s.RespectNullableAnnotations = true;
s.RespectRequiredConstructorParameters = true;
});


Для прямых вызовов JsonSerializer.Deserialize вне MVC-пайплайна — передавайте JsonSerializerOptions.Strict напрямую.

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

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

#il_люминатор

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

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

🙂 Больше памяти — больше проблем

Автор книг по исходникам Quake и Doom собрал ретро-машину 1997 года. Материнка, HDD — дорого. А вот 384 МБ SDRAM обошлись всего в $60. Почему бы не взять с запасом?

Запустил Quake. Pentium MMX 233MHz выдаёт стабильные 44 fps. Всё отлично, пишет статью, уходит на месяц. Возвращается, запускает тот же бенчмарк и результат: 33 fps.

Перебрал всё: видеокарты, драйверы, переустановка системы. Ничего не помогает. Случайно попробовал вытащить одну планку — 33 fps. Вытащил ещё одну — 44. Вернул обратно и опять 33.

Оказалось, чипсет материнской платы кешировал только ограниченный объём RAM через L2-кеш. Всё, что сверх лимита — работало напрямую, без кеша вообще. А Windows 95 загружается «сверху вниз» по адресному пространству, то есть некешируемая зона задействовалась сразу при старте.

➡️ Источник

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

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

#entry_point

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

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

🔥 4 привычки кодеров

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

но, я собрал 4 привычки адептов «чистого кода», (которые обычно все практикуют) 🤡

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

• Упарывание в сложную архитектуру

Сеньоры на ютубе обещают золотые горы, если внедрить микросервисы куда угодно. Итог: получаешь красивый overengineering-проект для мамы и 0 запущенных продуктов в срок, пока конкуренты клепают MVP на коленке.

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

• Ручная микро-оптимизация
Классика для тех, кто любит алгоритмы из универа. Итог: убиваешь дни жизни и выжимаешь миллисекунды, хотя бизнесу нужен был просто грязный, но рабочий скрипт еще вчера.

Проблема в том, что ни один из этих путей не дает самого главного - скорости и проверки гипотез. Реальному рынку плевать на твой идеальный код за 3 дня. Бизнес предпочтет код от ИИ-агента за 5 минут, который уже завтра начнет приносить деньги.

Хочешь обкатанный на нас лично и 100х учениках метод, как перестать кодить руками и начать делегировать задачи автономным системам?

👉 Заходи сюда, но у нас осталось всего 4 места, набор идет до завтрашнего дня.

P. S. Если интересно еще что-нибудь почитать от меня, то заходите в «Азбуку Айтишника», там я рассказываю об айти-базе, также у меня там есть бесплатный гайд на 15 глав по ии-агентам

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

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

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

🔗 Успеть на обучение

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

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

#garbage_collector

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

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

Урок истории

До .NET 4.5 асинхронный код писали вручную через обратные вызовы и явный маршалинг между потоками. Это было сложно, многословно и доступно только опытным разработчикам. C# 5 и Visual Basic с новым синтаксисом async/await убрали эту сложность.

Что происходит внутри

Компилятор превращает каждый async-метод в конечный автомат. Вот простой пример:

public static async Task SimpleBodyAsync() {
Console.WriteLine("Hello, Async World!");
}


После компиляции это превращается примерно в:
public static Task SimpleBodyAsync() {
var d = new <SimpleBodyAsync>d__0();
d.<>t__builder = AsyncTaskMethodBuilder.Create();
d.MoveNext();
return d.<>t__builder.Task;
}


Плюс отдельная struct с методом MoveNext, блоком try/catch и полями для хранения состояния. JIT не сможет встроить такой метод по месту вызова. Появляются издержки на вызов методов инфраструктуры SetResult, SetException и запись в поля конечного автомата.

Когда async лишний

Если метод всегда выполняется синхронно, оборачивать его в async нет смысла. Тауб приводит пример MemoryStream.ReadAsync: чтение из памяти и без того быстрое, и каждый вызов будет создавать новый объект Task<int> просто чтобы вернуть число.

Решение: возвращать кешированный результат вручную.
private Task<int> m_lastTask;

public override Task<int> ReadAsync(
byte[] buffer, int offset, int count,
CancellationToken cancellationToken)
{
int numRead = this.Read(buffer, offset, count);
return m_lastTask != null && numRead == m_lastTask.Result
? m_lastTask
: (m_lastTask = Task.FromResult(numRead));
}


Это позволяет при повторяющихся вызовах с одним и тем же результатом не создавать новые объекты совсем.

SynchronizationContext и лишние переходы

По умолчанию await захватывает текущий SynchronizationContext и возвращает продолжение в него. Для UI-потока это удобно: не нужно вручную делать маршалинг. Но в библиотечном коде это создаёт лишние переходы между потоками.

Если из UI-потока запустить цикл копирования с await на каждой операции чтения и записи мегабайта данных, получится более 500 переходов из фоновых потоков обратно в UI-поток. Чтобы этого избежать, в библиотеках следует использовать ConfigureAwait(false):

while ((numRead = await source.ReadAsync(buffer, 0, buffer.Length)
.ConfigureAwait(false)) > 0)
{
await destination.WriteAsync(buffer, 0, numRead)
.ConfigureAwait(false);
}


Без ConfigureAwait(false) в библиотечном коде возможна и взаимоблокировка: если вызывающий код в UI-потоке зовёт t.Wait(), а продолжение пытается вернуться в тот же заблокированный поток, оба будут ждать друг друга бесконечно.

Локальные переменные и сбор мусора

Компилятор поднимает все локальные переменные асинхронного метода в поля конечного автомата, который упаковывается в кучу при первом реальном ожидании. На момент выхода статьи компиляторы поднимали иногда больше переменных, чем нужно: даже те, что после await уже не читались.

// Лишнее поле в конечном автомате
public static async Task FooAsync() {
var dto = DateTimeOffset.Now;
var dt = dto.DateTime;
await Task.Yield();
Console.WriteLine(dt);
}

// Лучше так:
public static async Task FooAsync() {
var dt = DateTimeOffset.Now.DateTime;
await Task.Yield();
Console.WriteLine(dt);
}


Чем больше объектов создаётся, тем чаще срабатывает сборщик мусора. Это влияет на всю систему, а не только на конкретный метод.

Меньше await — лучше

Каждое await-выражение несёт накладные расходы. Если нужно подождать несколько задач, лучше объединить их через Task.WhenAll, чем ждать по одной:

// Хуже: три отдельных await
int ra = await a;
int rb = await b;
int rc = await c;

// Лучше: одно await на все три
int[] results = await Task.WhenAll(a, b, c);


async/await упростил жизнь разработчикам, но не отменил необходимость понимать, что происходит внутри.

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

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

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

#sharp_view

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

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

Смотрите, какую годноту нашли. Заказать вряд ли получится, но вдохновиться — вполне.

Это лимитированные ремни, но есть ещё кое-что более лимитированное — места на нашем курсе по разработке ИИ агентов! До 30 апреля осталось всего 4 места.

👉 Занять место по ссылке

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

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

#garbage_collector

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

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

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

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

#garbage_collector

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

Библиотека шарписта | 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

💡 sealed в C#: зачем закрывать классы

Модификатор sealed существует в языке давно, но в современных версиях .NET он приобрёл дополнительный вес за счёт оптимизаций JIT-компилятора.

Что делает sealed

sealed запрещает наследование. Если класс помечен как sealed, от него нельзя унаследоваться. Можно применять и к отдельным методам с override, тогда этот метод нельзя переопределить дальше по цепочке.

public sealed class PaymentConfiguration
{
public string ApiKey { get; set; }
public int TimeoutInSeconds { get; set; }
}

// Ошибка компиляции: нельзя наследоваться от sealed-типа
public class GatewayX : PaymentConfiguration { }


Почему это влияет на производительность

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

; Открытый класс — несколько обращений к памяти перед вызовом
mov eax, [edx]
mov eax, [eax+0x28]
call dword ptr [eax+0x10]


Для sealed-класса JIT точно знает, какой метод будет вызван. Он убирает обращение к vtable, встраивает тело метода прямо в место вызова и оставляет минимум инструкций:
; Sealed-класс — только проверка на null и возврат
cmp [edx], dl
ret


Дополнительно ускоряются операторы is и as, потому что среде не нужно проходить по всему дереву наследования.

Когда sealed не подойдёт

Entity Framework создаёт прокси-классы в рантайме для ленивой загрузки. Если сущность sealed, фреймворк не сможет её расширить:
// EF не сможет создать прокси для sealed-класса
public class Order
{
public int Id { get; set; }
public virtual ICollection<OrderItem> Items { get; set; }
}


Аналогичная проблема с библиотеками для мокирования (Moq, NSubstitute). Они создают наследников в рантайме, и sealed ломает это:
public sealed class IntegrationService
{
public bool SendData(string data) => true;
}

// Moq выбросит исключение: нельзя наследоваться от sealed-типа
var mock = new Mock<IntegrationService>();


Архитекторы рекомендуют подход "sealed by default": закрывать все классы сразу, а наследование открывать только когда оно действительно нужно.

Это защищает дизайн от случайных расширений и даёт прирост производительности без дополнительных усилий.

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

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

#il_люминатор

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

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

🛠 yield return под капотом

Многие используют yield return, но не задумываются что происходит внутри. Компилятор перестраивает весь метод в конечный автомат.

Что делает компилятор

Вот простой метод:

public IEnumerable<int> GetNumbers()
{
yield return 1;
yield return 2;
yield return 3;
}


Компилятор создаёт отдельный класс с полем _state. Каждый yield return становится отдельным состоянием в switch:
public bool MoveNext()
{
switch (_state)
{
case 0: _state = 1; _current = 1; return true;
case 1: _state = 2; _current = 2; return true;
case 2: _state = 3; _current = 3; return true;
default: return false;
}
}


foreach под капотом вызывает MoveNext() на каждой итерации. Локальные переменные метода становятся полями этого класса — так состояние и сохраняется между вызовами.

Главное свойство — ленивость

Код выполняется только при вызове MoveNext(). Поэтому можно работать с бесконечными последовательностями:
public IEnumerable<int> Infinite()
{
int i = 0;
while (true) yield return i++;
}

Infinite().Take(5); // {0, 1, 2, 3, 4} — не зависнет


Практически

При загрузке данных из БД yield return позволяет начать обработку до того, как все данные загрузятся:
public IEnumerable<Order> GetOrders(IDataReader reader)
{
while (reader.Read())
yield return new Order { Id = reader.GetInt32(0) };
}


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

При каждом вызове метода создаётся новый объект сгенерированного класса в куче. При частых вызовах это влияет на аллокации. async/await работает по той же схеме — это два применения одного паттерна трансформации кода.

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

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

#il_люминатор

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

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

🧑‍💻 SkiaSharp 4.0: движок обновлён, новый сопровождающий, переменные шрифты

Вышел первый превью SkiaSharp 4.0. Библиотека существует 10 лет и лежит в основе кроссплатформенной 2D-графики в .NET: её используют .NET MAUI, WebAssembly, WinUI 3. Это первый крупный мейджор за долгое время.

Что поменялось в движке

Главное обновление — переход на Skia milestone 147. Это 2,5 года апстрим-изменений, которые достаются автоматически без правки кода.

Качество изображений. Mipmap-шарпенинг включён по умолчанию, уменьшенные изображения стали чётче. Кодеки теперь читают Exif-метаданные и автоматически применяют поворот фото. Большие битмапы, которые не влезают в лимиты текстур GPU, тайлятся автоматически.

Цвет. Поправлены передаточные функции для Rec.709, HLG и PQ. Для тех, кто работает с видео или профессиональной цветокоррекцией, это важно.

Производительность. Незначительный прирост по всем операциям рендеринга. Более заметные улучшения в noise-шейдерах и canvas-операциях.

Безопасность. Обновлены нативные зависимости, включены современные митигации компилятора на всех платформах.

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

Переменные шрифты. Полная поддержка OpenType variable fonts через SkiaSharp и HarfBuzz. Можно получить доступные оси, задать их значения и создавать варианты шрифта по весу, ширине, наклону или кастомным осям.

Палитры цветных шрифтов. Поддержка OpenType CPAL для эмодзи и иконочных шрифтов. Можно переключать палитры или переопределять цвет отдельных глифов.

SKPathBuilder. Новый способ строить пути. SKPath теперь иммутабелен, а SKPathBuilder предоставляет привычный API с MoveTo, LineTo, CubicTo и фабриками фигур. Старые методы SKPath сохранены для обратной совместимости.

Новые платформы. Добавлены нативные сборки для Linux Bionic и Tizen x64/arm64.

Uno Platform стала сопровождающим

Вместе с релизом объявили, что Uno Platform становится co-maintainer SkiaSharp. Они используют библиотеку в собственном рендер-пайплайне и уже сделали значимые вклады: обновления движка Skia, полная реализация API переменных шрифтов, фикс краша с typeface на Android API 36, поддержка генератора биндингов на Linux, интерактивная Wasm-галерея.

Для тех, кто зависит от SkiaSharp в продакшне: библиотека теперь поддерживается двумя организациями, что ускорит обновления и тришаж.

➡️ Интерактивная галерея с примерами и шейдер-плейграунд.

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

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

#async_news

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

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

🌟 VSTest убирает зависимость от Newtonsoft.Json

Начиная с .NET 11 Preview 4 и Visual Studio 18.8, VSTest больше не будет тянуть Newtonsoft.Json. Вместо него на .NET будет использоваться System.Text.Json, на .NET Framework будет`JSONite`.

Почему

Все версии Newtonsoft.Json ниже 13.0.0 теперь помечены как уязвимые на NuGet.org. Это часть более широкой работы по удалению Newtonsoft.Json из .NET SDK.

Что не меняется

Формат сообщений VSTest остаётся прежним. Сериализация идентична независимо от используемой библиотеки. Старые тестовые хосты совместимы с обновлённой платформой и наоборот. Производительность сериализации не ухудшилась.

Кого затронет

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

1. Ошибка сборки — если тестовый проект использует типы Newtonsoft.Json (JObject, JsonConvert) без явной ссылки на пакет. Раньше пакет «протекал» через VSTest. Теперь нет.

Решение — добавить зависимость:

<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />


2. Ошибка в рантайме — если пакет подключён с <ExcludeAssets>runtime</ExcludeAssets> и проект рассчитывал на копию из VSTest. Тест упадёт с FileNotFoundException.

Решение — убрать <ExcludeAssets>runtime</ExcludeAssets>.

3. Ошибка загрузки адаптера — если тестовый адаптер или data collector использует Newtonsoft.Json без явной зависимости. Среди известных адаптеров таких случаев пока не обнаружено.

Превью-пакеты уже доступны на NuGet как Microsoft.TestPlatform.* версии 1.0.0-alpha-stj.

➡️ Источник

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

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

#async_news

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

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

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

Разработчик C# — от 365 000 ₽, удалёнка/гибрид в Москве, Санкт-Петербурге, или Казани

C# Developer (WinForms + SQL) — от 350 000 ₽, офис в Санкт-Петербурге

C#/. NET-разработчик — удалёнка

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

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

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

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

⭐️ C# превращает лямбду в метод

Лямбда-функция в C# это чуть больше, чем кусок кода. Компилятор переписывает ваш код, и если понять как именно, многое встаёт на своё место.

Простой случай

Когда лямбда не захватывает никаких переменных, компилятор просто превращает её в обычный метод:

public class LambdaDemo2
{
private System.Timers.Timer? _timer;

private void HiddenMethodForLambda(
object? sender, System.Timers.ElapsedEventArgs args)
{
Console.WriteLine("Выполнено");
}

public void InitTimer()
{
_timer = new System.Timers.Timer(1000);
_timer.Elapsed += HiddenMethodForLambda;
_timer.Enabled = true;
}
}


Тело лямбды перемещается в отдельный метод. Среда выполнения работает с ним как с обычным методом — никаких особых структур.

Когда лямбда захватывает переменную

Ситуация усложняется, если внутри лямбды используется локальная переменная из внешнего метода:
public void InitTimer()
{
int aVariable = 5;
_timer = new System.Timers.Timer(1000);
_timer.Elapsed += (sender, args) => Console.WriteLine(aVariable);
_timer.Enabled = true;
}


Здесь уже один метод не поможет: нужно где-то хранить aVariable так, чтобы к ней имели доступ сразу два места — InitTimer и лямбда. В .NET для этого используют классы.

Компилятор генерирует вспомогательный класс и переносит туда локальную переменную:
public class LambdaDemo4
{
private System.Timers.Timer? _timer;

private class HiddenClassForLambda
{
public int aVariable;

public void HiddenMethodForLambda(
object? sender, System.Timers.ElapsedEventArgs args)
{
Console.WriteLine(aVariable);
}
}

public void InitTimer()
{
var hiddenObject = new HiddenClassForLambda();
hiddenObject.aVariable = 5;
_timer = new System.Timers.Timer(1000);
_timer.Elapsed += hiddenObject.HiddenMethodForLambda;
_timer.Enabled = true;
}
}


Локальная переменная стала полем класса. Метод InitTimer и лямбда теперь обращаются к одному объекту hiddenObject.

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

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

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

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

#il_люминатор

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

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

💡 Версионирование API в .NET 10 вместе с OpenAPI

Когда API растёт, рано или поздно встаёт вопрос: как добавить новые возможности и не сломать тех, кто уже использует старую версию? Стандартный ответ — версионирование. В .NET 10 появился удобный способ совместить версионирование с OpenAPI-документацией без лишнего кода.

Зачем нужно версионирование

Без версионирования любое изменение контракта API потенциально ломает клиентов. Версионирование позволяет выпускать новые версии параллельно со старыми, пока клиенты не перейдут самостоятельно.

Популярные стратегии:
- По URL: /api/v1/users
- По query string: /api/users?api-version=1.0
- По заголовку: X-API-Version: 1.0

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

С .NET 9 Microsoft.AspNetCore.OpenApi стал стандартным инструментом для генерации OpenAPI вместо Swashbuckle.AspNetCore. Но удобной интеграции с версионированием не было.

В .NET 10 вышел пакет Asp.Versioning.OpenApi версии 10 — первый, который официально поддерживает и .NET 10, и новую OpenAPI-библиотеку от Microsoft.

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

Установите пакеты:

Asp.Versioning.Http@10.0.0
Asp.Versioning.Mvc.ApiExplorer@10.0.0
Asp.Versioning.OpenApi@10.0.0-rc.1


Настройка:
builder.Services.AddApiVersioning()
.AddApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVV";
})
.AddOpenApi();

app.MapOpenApi().WithDocumentPerVersion();


Регистрация эндпоинтов:
var usersApi = app.NewVersionedApi("Users");

var v1 = usersApi.MapGroup("api/users").HasApiVersion("1.0");
var v2 = usersApi.MapGroup("api/users").HasApiVersion("2.0");

v1.MapGet("", () => TypedResults.Ok(new[]
{
new UserV1(1, "John Doe"),
}));

v2.MapGet("", () => TypedResults.Ok(new[]
{
new UserV2(1, "John Doe", new DateOnly(1990, 1, 1)),
}));


После запуска OpenAPI-документы доступны по адресам /openapi/v1.json и /openapi/v2.json.

Как подключить контроллеры

Пакеты:
Asp.Versioning.Mvc@10.0.0
Asp.Versioning.Mvc.ApiExplorer@10.0.0
Asp.Versioning.OpenApi@10.0.0-rc.1


Настройка идентична Minimal APIs, только добавляется .AddMvc():
builder.Services.AddApiVersioning()
.AddApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVV";
})
.AddMvc()
.AddOpenApi();


Контроллеры с версиями:
[ApiController]
[Route("api/users")]
[ApiVersion("1.0")]
public class UsersV1Controller : ControllerBase
{
[HttpGet]
public ActionResult<UserV1[]> Get() =>
Ok(new[] { new UserV1(1, "John Doe") });
}

[ApiController]
[Route("api/users")]
[ApiVersion("2.0")]
public class UsersV2Controller : ControllerBase
{
[HttpGet]
public ActionResult<UserV2[]> Get() =>
Ok(new[] { new UserV2(1, "John Doe", new DateOnly(1990, 1, 1)) });
}


Визуализация: SwaggerUI и Scalar

Оба инструмента умеют показывать версионированные документы. SwaggerUI подключается через Swashbuckle.AspNetCore.SwaggerUI, Scalar через Scalar.AspNetCore.

SwaggerUI:
app.UseSwaggerUI(options =>
{
foreach (var desc in app.DescribeApiVersions().Reverse())
{
options.SwaggerEndpoint(
$"/openapi/{desc.GroupName}.json",
desc.GroupName.ToUpperInvariant());
}
});


Scalar:
app.MapScalarApiReference(options =>
{
var descriptions = app.DescribeApiVersions();
for (var i = 0; i < descriptions.Count; i++)
{
var desc = descriptions[i];
options.AddDocument(desc.GroupName, desc.GroupName,
isDefault: i == descriptions.Count - 1);
}
});


SwaggerUI откроется по /swagger, Scalar по /scalar.

Что изменилось по сравнению с v8

В старой версии Asp.Versioning.OpenApi v8 нужно было вызывать AddOpenApi() отдельно для каждой версии:
// v8
builder.Services.AddOpenApi("v1");
builder.Services.AddOpenApi("v2");


Теперь достаточно одного вызова, а WithDocumentPerVersion() берёт на себя генерацию отдельного документа для каждой версии автоматически.

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

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

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

#il_люминатор

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

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

🗓️ Уже через пару часов стартует вебинар!

Тема:

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


Ждем вас сегодня в 19:00 по московскому времени. Не пропустите начало, будет много практики!

👉 Успей занять место

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

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

#️⃣ hh.ru + Госуслуги + трудовая

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

Сильнее всего это бьёт по IT: здесь много фриланса, совмещений и серых периодов.

➡️ Куда скрутить накрученный опыт

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

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

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

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

🌐 REST DTO не должны попадать в сервис

Частая ошибка в .NET-проектах — использовать одни и те же модели на всех слоях. Контроллер принял CreateUserRequest, передал его прямо в сервис, сервис вернул UserResponse обратно в контроллер. Выглядит просто. Но это ловушка.

REST DTO это контракт с клиентом. Сервисный слой это бизнес-логика. Это разные ответственности.
Пример плохого кода:

// REST DTO
public class CreateUserRequest
{
public string Email { get; set; }
public string Password { get; set; }
public string Role { get; set; } // "admin", "user"
}

// Контроллер передаёт REST DTO прямо в сервис
[HttpPost]
public async Task<IActionResult> Create(CreateUserRequest request)
{
var user = await _userService.CreateAsync(request); // не надо
return Ok(user);
}

// Сервис знает о REST DTO
public async Task<UserResponse> CreateAsync(CreateUserRequest request)
{
// бизнес-логика завязана на HTTP-контракт
}


Теперь представьте, что появился второй клиент. Он шлёт другой формат. Или вы хотите вызвать CreateAsync из фоновой задачи, где нет никакого HTTP-запроса. Приходится либо тащить ненужный DTO, либо переписывать сервис.

Ещё хуже поле Role в CreateUserRequest. Клиент сам указывает, кем хочет стать. Даже если оно не используется или перезаписывается далее, выглядит это не очень.

Как правильно

Каждый слой работает со своей моделью. Контроллер маппит входящий DTO в команду или модель сервисного слоя. Сервис возвращает доменный результат. Контроллер маппит его в ответный DTO:
// REST DTO — только для HTTP-слоя
public class CreateUserRequest
{
public string Email { get; set; }
public string Password { get; set; }
// Role здесь нет — клиент не решает
}

public class UserResponse
{
public Guid Id { get; set; }
public string Email { get; set; }
public string Role { get; set; }
}

// Сервисная модель — внутренний контракт
public class CreateUserCommand
{
public string Email { get; set; }
public string Password { get; set; }
public UserRole Role { get; set; } // enum, не строка
}

public class UserResult
{
public Guid Id { get; set; }
public string Email { get; set; }
public UserRole Role { get; set; }
}


// Контроллер — маппит и не лезет в логику
[HttpPost]
public async Task<IActionResult> Create(CreateUserRequest request)
{
var command = new CreateUserCommand
{
Email = request.Email,
Password = request.Password,
Role = UserRole.User // роль задаётся здесь, не клиентом
};

var result = await _userService.CreateAsync(command);

var response = new UserResponse
{
Id = result.Id,
Email = result.Email,
Role = result.Role.ToString()
};

return Ok(response);
}

// Сервис — ничего не знает про HTTP
public async Task<UserResult> CreateAsync(CreateUserCommand command)
{
// чистая бизнес-логика
}


Почему это важно

Сервис перестаёт зависеть от формата HTTP-запроса. Его можно вызвать из воркера, gRPC-эндпоинта, теста без изменений.

Контракт с клиентом можно менять независимо от внутренней логики. Добавили поле в CreateUserRequest, сервис об этом не знает.

Валидация и маппинг живут в одном месте — в контроллере. Сервис получает уже проверенные данные в нужном формате.

Для маппинга удобно использовать AutoMapper или Mapster, но даже ручной маппинг лучше, чем слитые слои.

Граница между HTTP-слоем и бизнес-логикой — это не формальность. Это то, что позволяет менять одно, не трогая другое.

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

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

#il_люминатор

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

Библиотека шарписта | 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_люминатор

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